From 4ce7b5743eb9a2fe641968316f69a831aea532ef Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 19:24:43 -0600 Subject: [PATCH 01/23] [#.x] - folder rename --- src/Domain/Infrastructure/Encryption/JWTToken.php | 2 +- .../Infrastructure/Encryption/JWTTokenTest.php | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Domain/Infrastructure/Encryption/JWTToken.php b/src/Domain/Infrastructure/Encryption/JWTToken.php index a00026e..2c242c7 100644 --- a/src/Domain/Infrastructure/Encryption/JWTToken.php +++ b/src/Domain/Infrastructure/Encryption/JWTToken.php @@ -18,7 +18,7 @@ use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Common\FlagsEnum; use Phalcon\Api\Domain\Infrastructure\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; diff --git a/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php b/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php index 5236ae0..69130c2 100644 --- a/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php +++ b/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php @@ -13,9 +13,8 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Encryption; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; use Phalcon\Api\Domain\Infrastructure\Exceptions\TokenValidationException; use Phalcon\Api\Tests\AbstractUnitTestCase; @@ -29,13 +28,13 @@ public function setUp(): void { parent::setUp(); - $this->jwtToken = $this->container->get(Container::JWT_TOKEN); + $this->jwtToken = $this->container->get(JWTToken::class); } public function testGetForUserReturnsTokenString(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $userData = $this->getUserData(); $domainUser = $userMapper->domain($userData); @@ -47,7 +46,7 @@ public function testGetForUserReturnsTokenString(): void public function testGetObjectReturnsPlainToken(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $userData = $this->getUserData(); $domainUser = $userMapper->domain($userData); @@ -77,7 +76,7 @@ public function testGetObjectThrowsOnInvalidTokenStructure(): void public function testGetUserReturnsUserArray(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $userData = $this->getUserData(); $domainUser = $userMapper->domain($userData); @@ -107,7 +106,7 @@ public function testGetUserReturnsUserArray(): void public function testValidateSuccess(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $userData = $this->getUserData(); $domainUser = $userMapper->domain($userData); From c1e7f6b5a59c3c202a3959b50516b8c48ee354b3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 19:25:55 -0600 Subject: [PATCH 02/23] [#.x] - refactoring fixtures; added new fixtures --- .../Application/Command/CommandFixture.php | 11 ++++---- .../Application/Handler/HandlerFixture.php | 26 +++++++++++++++++++ .../Service}/ServiceFixture.php | 2 +- tests/Fixtures/Domain/Components/Env/.env | 4 --- 4 files changed, 32 insertions(+), 11 deletions(-) rename src/Domain/Services/User/AbstractUserService.php => tests/Fixtures/Domain/Application/Command/CommandFixture.php (51%) create mode 100644 tests/Fixtures/Domain/Application/Handler/HandlerFixture.php rename tests/Fixtures/Domain/{Services => Application/Service}/ServiceFixture.php (88%) delete mode 100644 tests/Fixtures/Domain/Components/Env/.env diff --git a/src/Domain/Services/User/AbstractUserService.php b/tests/Fixtures/Domain/Application/Command/CommandFixture.php similarity index 51% rename from src/Domain/Services/User/AbstractUserService.php rename to tests/Fixtures/Domain/Application/Command/CommandFixture.php index 347ef53..d14f56b 100644 --- a/src/Domain/Services/User/AbstractUserService.php +++ b/tests/Fixtures/Domain/Application/Command/CommandFixture.php @@ -11,18 +11,17 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\User; +namespace Phalcon\Api\Tests\Fixtures\Domain\Application\Command; -use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Facades\UserFacade; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; -abstract class AbstractUserService implements DomainInterface +final readonly class CommandFixture implements CommandInterface { /** - * @param UserFacade $facade + * @param string|null $name */ public function __construct( - protected readonly UserFacade $facade, + public ?string $name, ) { } } diff --git a/tests/Fixtures/Domain/Application/Handler/HandlerFixture.php b/tests/Fixtures/Domain/Application/Handler/HandlerFixture.php new file mode 100644 index 0000000..1aa8443 --- /dev/null +++ b/tests/Fixtures/Domain/Application/Handler/HandlerFixture.php @@ -0,0 +1,26 @@ + + * + * 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\Application\Handler; + +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\CommandBus\HandlerInterface; + +final readonly class HandlerFixture implements HandlerInterface +{ + public function __invoke(CommandInterface $command): Payload + { + return Payload::success(['Name: ' . $command->name]); + } +} diff --git a/tests/Fixtures/Domain/Services/ServiceFixture.php b/tests/Fixtures/Domain/Application/Service/ServiceFixture.php similarity index 88% rename from tests/Fixtures/Domain/Services/ServiceFixture.php rename to tests/Fixtures/Domain/Application/Service/ServiceFixture.php index c47933b..879c8a7 100644 --- a/tests/Fixtures/Domain/Services/ServiceFixture.php +++ b/tests/Fixtures/Domain/Application/Service/ServiceFixture.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Fixtures\Domain\Services; +namespace Phalcon\Api\Tests\Fixtures\Domain\Application\Service; use Phalcon\Api\Domain\ADR\DomainInterface; use Phalcon\Api\Domain\ADR\Payload; diff --git a/tests/Fixtures/Domain/Components/Env/.env b/tests/Fixtures/Domain/Components/Env/.env deleted file mode 100644 index b707f0d..0000000 --- a/tests/Fixtures/Domain/Components/Env/.env +++ /dev/null @@ -1,4 +0,0 @@ -SAMPLE_STRING=sample_value -SAMPLE_INT=1 -SAMPLE_TRUE=true -SAMPLE_FALSE=false From d151172cf81890757ee4d0487e5a1d0df18bf00f Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 19:26:52 -0600 Subject: [PATCH 03/23] [#.x] - referencing di services by class name --- .../Middleware/AbstractMiddleware.php | 3 +- .../ValidateTokenClaimsMiddleware.php | 12 ++++---- .../ValidateTokenPresenceMiddleware.php | 2 +- .../ValidateTokenRevokedMiddleware.php | 7 +++-- .../ValidateTokenStructureMiddleware.php | 25 ++++++++-------- .../ValidateTokenUserMiddleware.php | 16 +++++----- .../ValidateTokenClaimsMiddlewareTest.php | 29 ++++++++++--------- .../ValidateTokenPresenceMiddlewareTest.php | 6 ++-- .../ValidateTokenRevokedMiddlewareTest.php | 16 +++++----- .../ValidateTokenStructureMiddlewareTest.php | 10 +++---- .../ValidateTokenUserMiddlewareTest.php | 15 +++++----- 11 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php index 3fed350..22cdba4 100644 --- a/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php @@ -16,6 +16,7 @@ use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Responder\JsonResponder; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Responder\ResponderTypes; use Phalcon\Events\Exception as EventsException; @@ -88,7 +89,7 @@ protected function halt( /** @var ResponseInterface $response */ $response = $application->getSharedService(Container::RESPONSE); /** @var ResponderInterface $responder */ - $responder = $application->getService(Container::RESPONDER_JSON); + $responder = $application->getService(JsonResponder::class); $application->stop(); diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php index 3dfb26e..7e12351 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php @@ -13,9 +13,9 @@ namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Events\Exception as EventsException; @@ -37,10 +37,10 @@ final class ValidateTokenClaimsMiddleware extends AbstractMiddleware */ public function call(Micro $application): bool { - /** @var JWTToken $jwtToken */ - $jwtToken = $application->getSharedService(Container::JWT_TOKEN); /** @var Registry $registry */ - $registry = $application->getSharedService(Container::REGISTRY); + $registry = $application->getSharedService(Registry::class); + /** @var TokenManagerInterface $tokenManager */ + $tokenManager = $application->getSharedService(TokenManager::class); /** * Get the token object @@ -61,7 +61,7 @@ public function call(Micro $application): bool * user stored in the session */ /** @var array $errors */ - $errors = $jwtToken->validate($tokenObject, $sessionUser); + $errors = $tokenManager->validate($tokenObject, $sessionUser); if (true !== empty($errors)) { $this->halt( diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php index 7c08858..e4e361b 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php @@ -35,7 +35,7 @@ public function call(Micro $application): bool /** @var Request $request */ $request = $application->getSharedService(Container::REQUEST); /** @var EnvManager $env */ - $env = $application->getSharedService(Container::ENV); + $env = $application->getSharedService(EnvManager::class); if (true === $this->isEmptyBearerToken($request, $env)) { $this->halt( diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php index cd93845..2f0a40f 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php @@ -18,6 +18,7 @@ use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Cache\Cache; use Phalcon\Http\RequestInterface; use Phalcon\Mvc\Micro; use Phalcon\Support\Registry; @@ -35,11 +36,11 @@ public function call(Micro $application): bool /** @var RequestInterface $request */ $request = $application->getSharedService(Container::REQUEST); /** @var CacheInterface $cache */ - $cache = $application->getSharedService(Container::CACHE); + $cache = $application->getSharedService(Cache::class); /** @var EnvManager $env */ - $env = $application->getSharedService(Container::ENV); + $env = $application->getSharedService(EnvManager::class); /** @var Registry $registry */ - $registry = $application->getSharedService(Container::REGISTRY); + $registry = $application->getSharedService(Registry::class); /** @var User $domainUser */ $domainUser = $registry->get('user'); diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php index dc1535a..fdb9db6 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php @@ -14,10 +14,10 @@ namespace Phalcon\Api\Domain\Infrastructure\Middleware; use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; -use Phalcon\Api\Domain\Infrastructure\Exceptions\TokenValidationException; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Request; use Phalcon\Http\Response\Exception; @@ -38,22 +38,21 @@ public function call(Micro $application): bool /** @var Request $request */ $request = $application->getSharedService(Container::REQUEST); /** @var EnvManager $env */ - $env = $application->getSharedService(Container::ENV); - /** @var JWTToken $jwtToken */ - $jwtToken = $application->getSharedService(Container::JWT_TOKEN); + $env = $application->getSharedService(EnvManager::class); + /** @var TokenManagerInterface $tokenManager */ + $tokenManager = $application->getSharedService(TokenManager::class); - try { - $token = $jwtToken->getObject( - $this->getBearerTokenFromHeader($request, $env) - ); - } catch (TokenValidationException $ex) { + $hederToken = $this->getBearerTokenFromHeader($request, $env); + $tokenObject = $tokenManager->getObject($hederToken); + + if (null === $tokenObject) { $this->halt( $application, HttpCodesEnum::Unauthorized->value, HttpCodesEnum::Unauthorized->text(), [], [ - [$ex->getCode() => $ex->getMessage()], + [$tokenManager->getErrorMessage()], ] ); @@ -64,8 +63,8 @@ public function call(Micro $application): bool * If we are down here the token is an object and is valid */ /** @var Registry $registry */ - $registry = $application->getSharedService(Container::REGISTRY); - $registry->set('token', $token); + $registry = $application->getSharedService(Registry::class); + $registry->set('token', $tokenObject); return true; } diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php index 4dc4ec1..e0899dd 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php @@ -13,9 +13,9 @@ namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; -use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Events\Exception as EventsException; @@ -36,19 +36,19 @@ final class ValidateTokenUserMiddleware extends AbstractMiddleware */ public function call(Micro $application): bool { - /** @var JWTToken $jwtToken */ - $jwtToken = $application->getSharedService(Container::JWT_TOKEN); /** @var UserRepository $repository */ - $repository = $application->getSharedService(Container::USER_REPOSITORY); + $repository = $application->getSharedService(UserRepository::class); /** @var Registry $registry */ - $registry = $application->getSharedService(Container::REGISTRY); + $registry = $application->getSharedService(Registry::class); + /** @var TokenManagerInterface $tokenManager */ + $tokenManager = $application->getSharedService(TokenManager::class); /** * Get the token object */ /** @var Token $tokenObject */ $tokenObject = $registry->get('token'); - $domainUser = $jwtToken->getUser($repository, $tokenObject); + $domainUser = $tokenManager->getUser($repository, $tokenObject); if (null === $domainUser) { $this->halt( diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php index d4bcf8c..5cad5e3 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php @@ -13,8 +13,9 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenClaimsMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Mvc\Micro; @@ -69,25 +70,25 @@ public function testValidateTokenClaimsFailure( array $expectedErrors ): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); - [$micro, $middleware, $jwtToken] = $this->setupTest(); + [$micro, $middleware, $tokenManager] = $this->setupTest(); /** * Make the signature non valid */ $tokenUser = array_replace($user, $userData); $token = $this->getUserToken($tokenUser); - $tokenObject = $jwtToken->getObject($token); + $tokenObject = $tokenManager->getObject($token); $domainUser = $userMapper->domain($user); /** * Store the user in the registry */ /** @var Registry $registry */ - $registry = $this->container->get(Container::REGISTRY); + $registry = $this->container->get(Registry::class); $registry->set('user', $domainUser); $registry->set('token', $tokenObject); @@ -117,21 +118,21 @@ public function testValidateTokenClaimsFailure( public function testValidateTokenClaimsSuccess(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); $tokenUser = $userMapper->domain($user); - [$micro, $middleware, $jwtToken] = $this->setupTest(); + [$micro, $middleware, $tokenManager] = $this->setupTest(); $token = $this->getUserToken($user); - $tokenObject = $jwtToken->getObject($token); + $tokenObject = $tokenManager->getObject($token); /** * Store the user in the registry */ /** @var Registry $registry */ - $registry = $this->container->get(Container::REGISTRY); + $registry = $this->container->get(Registry::class); $registry->set('user', $tokenUser); $registry->set('token', $tokenObject); @@ -155,10 +156,10 @@ public function testValidateTokenClaimsSuccess(): void */ private function setupTest(): array { - $micro = new Micro($this->container); - $middleware = $this->container->get(Container::MIDDLEWARE_VALIDATE_TOKEN_CLAIMS); - $jwtToken = $this->container->getShared(Container::JWT_TOKEN); + $micro = new Micro($this->container); + $middleware = $this->container->get(ValidateTokenClaimsMiddleware::class); + $tokenManager = $this->container->getShared(TokenManager::class); - return [$micro, $middleware, $jwtToken]; + return [$micro, $middleware, $tokenManager]; } } diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php index e5d5111..b81f66b 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php @@ -13,8 +13,8 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenPresenceMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -25,7 +25,7 @@ final class ValidateTokenPresenceMiddlewareTest extends AbstractUnitTestCase public function testValidateTokenPresenceFailure(): void { $micro = new Micro($this->container); - $middleware = $this->container->get(Container::MIDDLEWARE_VALIDATE_TOKEN_PRESENCE); + $middleware = $this->container->get(ValidateTokenPresenceMiddleware::class); /** * No token exception @@ -54,7 +54,7 @@ public function testValidateTokenPresenceFailure(): void public function testValidateTokenPresenceSuccess(): void { $micro = new Micro($this->container); - $middleware = $this->container->get(Container::MIDDLEWARE_VALIDATE_TOKEN_PRESENCE); + $middleware = $this->container->get(ValidateTokenPresenceMiddleware::class); /** * No token return diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php index c0c04a6..4d8baee 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php @@ -14,9 +14,9 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenRevokedMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Cache\Cache; @@ -30,7 +30,7 @@ final class ValidateTokenRevokedMiddlewareTest extends AbstractUnitTestCase public function testValidateTokenRevokedFailureInvalidToken(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); $tokenUser = $userMapper->domain($user); @@ -43,7 +43,7 @@ public function testValidateTokenRevokedFailureInvalidToken(): void * Store the user in the registry */ /** @var Registry $registry */ - $registry = $this->container->get(Container::REGISTRY); + $registry = $this->container->get(Registry::class); $registry->set('user', $tokenUser); // There is no entry in the cache for this token, so this should fail. @@ -73,7 +73,7 @@ public function testValidateTokenRevokedFailureInvalidToken(): void public function testValidateTokenRevokedSuccess(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); $tokenUser = $userMapper->domain($user); @@ -86,11 +86,11 @@ public function testValidateTokenRevokedSuccess(): void * Store the user in the registry */ /** @var Registry $registry */ - $registry = $this->container->get(Container::REGISTRY); + $registry = $this->container->get(Registry::class); $registry->set('user', $tokenUser); /** @var Cache $cache */ - $cache = $micro->getSharedService(Container::CACHE); + $cache = $micro->getSharedService(Cache::class); $sessionUser = $registry->get('user'); $cacheKey = CacheConstants::getCacheTokenKey($sessionUser, $token); $payload = [ @@ -119,7 +119,7 @@ public function testValidateTokenRevokedSuccess(): void private function setupTest(): array { $micro = new Micro($this->container); - $middleware = $this->container->get(Container::MIDDLEWARE_VALIDATE_TOKEN_REVOKED); + $middleware = $this->container->get(ValidateTokenRevokedMiddleware::class); return [$micro, $middleware]; } diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php index bd96962..51d5038 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php @@ -13,9 +13,9 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenStructureMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -84,13 +84,13 @@ public function testValidateTokenStructureFailureNoDots(): void public function testValidateTokenStructureSuccess(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $userData = $this->getNewUserData(); $domainUser = $userMapper->domain($userData); [$micro, $middleware] = $this->setupTest(); /** @var JWTToken $jwtToken */ - $jwtToken = $micro->getSharedService(Container::JWT_TOKEN); + $jwtToken = $micro->getSharedService(JWTToken::class); $token = $jwtToken->getForUser($domainUser); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); @@ -114,7 +114,7 @@ public function testValidateTokenStructureSuccess(): void private function setupTest(): array { $micro = new Micro($this->container); - $middleware = $this->container->get(Container::MIDDLEWARE_VALIDATE_TOKEN_STRUCTURE); + $middleware = $this->container->get(ValidateTokenStructureMiddleware::class); return [$micro, $middleware]; } diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php index c412f1d..799a083 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php @@ -13,9 +13,10 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenUserMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Mvc\Micro; @@ -33,7 +34,7 @@ public function testValidateTokenUserFailureRecordNotFound(): void $token = $this->getUserToken($userData); $tokenObject = $jwtToken->getObject($token); /** @var Registry $registry */ - $registry = $this->container->get(Container::REGISTRY); + $registry = $this->container->get(Registry::class); $registry->set('token', $tokenObject); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); @@ -62,7 +63,7 @@ public function testValidateTokenUserFailureRecordNotFound(): void public function testValidateTokenUserSuccess(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); $domainUser = $userMapper->domain($user); @@ -72,7 +73,7 @@ public function testValidateTokenUserSuccess(): void $token = $jwtToken->getForUser($domainUser); $tokenObject = $jwtToken->getObject($token); /** @var Registry $registry */ - $registry = $this->container->get(Container::REGISTRY); + $registry = $this->container->get(Registry::class); $registry->set('token', $tokenObject); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); @@ -96,8 +97,8 @@ public function testValidateTokenUserSuccess(): void private function setupTest(): array { $micro = new Micro($this->container); - $middleware = $this->container->get(Container::MIDDLEWARE_VALIDATE_TOKEN_USER); - $jwtToken = $this->container->get(Container::JWT_TOKEN); + $middleware = $this->container->get(ValidateTokenUserMiddleware::class); + $jwtToken = $this->container->get(JWTToken::class); return [$micro, $middleware, $jwtToken]; } From c1685ed5d8484917d2d027e0f5fa0207c455dc6e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 19:31:35 -0600 Subject: [PATCH 04/23] [#.x] - referencing di services by class name; phpcs --- src/Domain/ADR/Payload.php | 10 +- .../User/Service}/UserPostService.php | 2 +- .../User/Service}/UserPutService.php | 2 +- .../DataSource/AbstractSanitizer.php | 4 +- .../DataSource/AbstractValueObject.php | 2 +- .../AuthSanitizer.php | 6 +- .../DataSource/Interfaces/MapperInterface.php | 49 --- .../Interfaces/SanitizerInterface.php | 31 -- .../DataSource/User/DTO/User.php | 36 +- .../DataSource/User/Facades/UserFacade.php | 351 ------------------ .../User/{Mappers => Mapper}/UserMapper.php | 10 +- .../UserRepository.php | 4 +- .../UserRepositoryInterface.php | 2 +- .../UserSanitizer.php | 6 +- .../UserValidator.php | 4 +- .../UserValidatorUpdate.php | 4 +- .../DataSource/Validation/Result.php | 2 +- .../Infrastructure/Enums/Http/RoutesEnum.php | 52 ++- .../AuthSanitizerEnum.php} | 4 +- .../UserSanitizerEnum.php} | 4 +- .../UserUpdateEnum.php | 19 +- .../Providers/ErrorHandlerProvider.php | 4 +- .../Providers/RouterProvider.php | 3 +- tests/AbstractUnitTestCase.php | 4 +- tests/Unit/Action/ActionHandlerTest.php | 5 +- tests/Unit/Domain/ADR/InputTest.php | 8 +- tests/Unit/Domain/ADR/PayloadTest.php | 100 ++--- .../Auth/Service}/LoginPostServiceTest.php | 21 +- .../Auth/Service}/LogoutPostServiceTest.php | 47 ++- .../Auth/Service}/RefreshPostServiceTest.php | 41 +- .../User/Service}/UserServiceDeleteTest.php | 11 +- .../User/Service}/UserServiceDispatchTest.php | 11 +- .../User/Service}/UserServiceGetTest.php | 13 +- .../User/Service}/UserServicePostTest.php | 76 ++-- .../User/Service}/UserServicePutTest.php | 120 +++--- .../Infrastructure/Constants/CacheTest.php | 8 +- .../Infrastructure/Constants/DatesTest.php | 2 +- .../DataSource/Auth/DTO/AuthInputTest.php | 5 +- .../AuthSanitizerTest.php | 5 +- .../DataSource/User/DTO/UserInputTest.php | 10 +- .../{Mappers => Mapper}/UserMapperTest.php | 63 ++-- .../UserRepositoryTest.php | 9 +- .../UserSanitizerTest.php | 4 +- .../Validator/UserValidatorUpdateTest.php | 88 +++++ .../Validators/UserValidatorUpdateTest.php | 80 ---- .../DataSource/Validation/ResultTest.php | 28 +- .../Encryption/SecurityTest.php | 3 +- .../Encryption/TokenCacheTest.php | 7 +- .../Enums/Http/RoutesEnumTest.php | 43 ++- .../Env/Adapters/DotEnvTest.php | 5 +- .../Infrastructure/Env/EnvManagerTest.php | 19 +- .../Middleware/HealthMiddlewareTest.php | 2 +- .../Middleware/NotFoundMiddlewareTest.php | 4 +- .../Providers/CacheDataProviderTest.php | 3 +- .../Providers/ErrorHandlerProviderTest.php | 8 +- tests/Unit/Responder/JsonResponderTest.php | 7 +- 56 files changed, 565 insertions(+), 906 deletions(-) rename src/Domain/{Services/User => Application/User/Service}/UserPostService.php (92%) rename src/Domain/{Services/User => Application/User/Service}/UserPutService.php (92%) rename src/Domain/Infrastructure/DataSource/Auth/{Sanitizers => Sanitizer}/AuthSanitizer.php (76%) delete mode 100644 src/Domain/Infrastructure/DataSource/Interfaces/MapperInterface.php delete mode 100644 src/Domain/Infrastructure/DataSource/Interfaces/SanitizerInterface.php delete mode 100644 src/Domain/Infrastructure/DataSource/User/Facades/UserFacade.php rename src/Domain/Infrastructure/DataSource/User/{Mappers => Mapper}/UserMapper.php (91%) rename src/Domain/Infrastructure/DataSource/User/{Repositories => Repository}/UserRepository.php (97%) rename src/Domain/Infrastructure/DataSource/User/{Repositories => Repository}/UserRepositoryInterface.php (99%) rename src/Domain/Infrastructure/DataSource/User/{Sanitizers => Sanitizer}/UserSanitizer.php (76%) rename src/Domain/Infrastructure/DataSource/User/{Validators => Validator}/UserValidator.php (87%) rename src/Domain/Infrastructure/DataSource/User/{Validators => Validator}/UserValidatorUpdate.php (87%) rename src/Domain/Infrastructure/Enums/{Sanitizers/AuthSanitizersEnum.php => Sanitizer/AuthSanitizerEnum.php} (82%) rename src/Domain/Infrastructure/Enums/{Sanitizers/UserSanitizersEnum.php => Sanitizer/UserSanitizerEnum.php} (91%) rename src/Domain/Infrastructure/Enums/{Validators => Validator}/UserUpdateEnum.php (69%) rename tests/Unit/Domain/{Services/Auth => Application/Auth/Service}/LoginPostServiceTest.php (87%) rename tests/Unit/Domain/{Services/Auth => Application/Auth/Service}/LogoutPostServiceTest.php (85%) rename tests/Unit/Domain/{Services/Auth => Application/Auth/Service}/RefreshPostServiceTest.php (85%) rename tests/Unit/Domain/{Services/User => Application/User/Service}/UserServiceDeleteTest.php (87%) rename tests/Unit/Domain/{Services/User => Application/User/Service}/UserServiceDispatchTest.php (88%) rename tests/Unit/Domain/{Services/User => Application/User/Service}/UserServiceGetTest.php (91%) rename tests/Unit/Domain/{Services/User => Application/User/Service}/UserServicePostTest.php (81%) rename tests/Unit/Domain/{Services/User => Application/User/Service}/UserServicePutTest.php (79%) rename tests/Unit/Domain/Infrastructure/DataSource/Auth/{Sanitizers => Sanitizer}/AuthSanitizerTest.php (89%) rename tests/Unit/Domain/Infrastructure/DataSource/User/{Mappers => Mapper}/UserMapperTest.php (82%) rename tests/Unit/Domain/Infrastructure/DataSource/User/{Repositories => Repository}/UserRepositoryTest.php (92%) rename tests/Unit/Domain/Infrastructure/DataSource/User/{Sanitizers => Sanitizer}/UserSanitizerTest.php (97%) create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/User/Validator/UserValidatorUpdateTest.php delete mode 100644 tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php diff --git a/src/Domain/ADR/Payload.php b/src/Domain/ADR/Payload.php index 68c2c83..13d45be 100644 --- a/src/Domain/ADR/Payload.php +++ b/src/Domain/ADR/Payload.php @@ -30,8 +30,8 @@ final class Payload extends PhalconPayload { /** - * @param string $status - * @param TPayloadDataInput $data + * @param string $status + * @param TPayloadDataInput $data * @param TPayloadDataInput $errors */ private function __construct( @@ -121,7 +121,7 @@ public static function unauthorized(array $errors): self { return new self( status: DomainStatus::UNAUTHORIZED, - errors:[ + errors: [ 'code' => HttpCodesEnum::Unauthorized->value, 'message' => HttpCodesEnum::Unauthorized->text(), 'data' => [], @@ -140,7 +140,7 @@ public static function updated(array $data): self return new self( DomainStatus::UPDATED, [ - 'data' => $data + 'data' => $data, ] ); } @@ -152,7 +152,7 @@ public static function updated(array $data): self * * @param TPayloadDataInput $existing * @param TPayloadDataInput $element - * @param string $key + * @param string $key * * @return TPayloadDataInput */ diff --git a/src/Domain/Services/User/UserPostService.php b/src/Domain/Application/User/Service/UserPostService.php similarity index 92% rename from src/Domain/Services/User/UserPostService.php rename to src/Domain/Application/User/Service/UserPostService.php index 91928c0..0710eda 100644 --- a/src/Domain/Services/User/UserPostService.php +++ b/src/Domain/Application/User/Service/UserPostService.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\User; +namespace Phalcon\Api\Domain\Application\User\Service; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; diff --git a/src/Domain/Services/User/UserPutService.php b/src/Domain/Application/User/Service/UserPutService.php similarity index 92% rename from src/Domain/Services/User/UserPutService.php rename to src/Domain/Application/User/Service/UserPutService.php index cb74dd7..3ff3152 100644 --- a/src/Domain/Services/User/UserPutService.php +++ b/src/Domain/Application/User/Service/UserPutService.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\User; +namespace Phalcon\Api\Domain\Application\User\Service; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; diff --git a/src/Domain/Infrastructure/DataSource/AbstractSanitizer.php b/src/Domain/Infrastructure/DataSource/AbstractSanitizer.php index bd61f7d..18f2bb8 100644 --- a/src/Domain/Infrastructure/DataSource/AbstractSanitizer.php +++ b/src/Domain/Infrastructure/DataSource/AbstractSanitizer.php @@ -14,8 +14,8 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; -use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers\SanitizersEnumInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interface\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizer\SanitizersEnumInterface; use Phalcon\Filter\FilterInterface; /** diff --git a/src/Domain/Infrastructure/DataSource/AbstractValueObject.php b/src/Domain/Infrastructure/DataSource/AbstractValueObject.php index b3be385..f0f7e0d 100644 --- a/src/Domain/Infrastructure/DataSource/AbstractValueObject.php +++ b/src/Domain/Infrastructure/DataSource/AbstractValueObject.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interface\SanitizerInterface; /** * Base factory for value objects such as input DTOs. diff --git a/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php b/src/Domain/Infrastructure/DataSource/Auth/Sanitizer/AuthSanitizer.php similarity index 76% rename from src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php rename to src/Domain/Infrastructure/DataSource/Auth/Sanitizer/AuthSanitizer.php index 8f8c6ac..c3a6da2 100644 --- a/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Sanitizer/AuthSanitizer.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizer; use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractSanitizer; -use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers\AuthSanitizersEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizer\AuthSanitizerEnum; final class AuthSanitizer extends AbstractSanitizer { - protected string $enum = AuthSanitizersEnum::class; + protected string $enum = AuthSanitizerEnum::class; } diff --git a/src/Domain/Infrastructure/DataSource/Interfaces/MapperInterface.php b/src/Domain/Infrastructure/DataSource/Interfaces/MapperInterface.php deleted file mode 100644 index 4392806..0000000 --- a/src/Domain/Infrastructure/DataSource/Interfaces/MapperInterface.php +++ /dev/null @@ -1,49 +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\Domain\Infrastructure\DataSource\Interfaces; - -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; - -/** - * Contract for mapping between domain DTO/objects and persistence arrays. - * - * @phpstan-import-type TUser from UserTypes - * @phpstan-import-type TUserDbRecord from UserTypes - * @phpstan-import-type TUserDomainToDbRecord from UserTypes - */ -interface MapperInterface -{ - /** - * Map Domain User -> DB row (usr_*) - * - * @return TUserDomainToDbRecord - */ - public function db(UserInput $user): array; - - /** - * Map DB row (usr_*) -> Domain User - * - * @param TUserDbRecord|array{} $row - */ - public function domain(array $row): User; - - /** - * Map input row -> Domain User - * - * @param TUser $row - */ - public function input(array $row): User; -} diff --git a/src/Domain/Infrastructure/DataSource/Interfaces/SanitizerInterface.php b/src/Domain/Infrastructure/DataSource/Interfaces/SanitizerInterface.php deleted file mode 100644 index 543430d..0000000 --- a/src/Domain/Infrastructure/DataSource/Interfaces/SanitizerInterface.php +++ /dev/null @@ -1,31 +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\Domain\Infrastructure\DataSource\Interfaces; - -use Phalcon\Api\Domain\ADR\InputTypes; - -/** - * @phpstan-import-type TInputSanitize from InputTypes - */ -interface SanitizerInterface -{ - /** - * Return a sanitized array of the input - * - * @param TInputSanitize $input - * - * @return TInputSanitize - */ - public function sanitize(array $input): array; -} diff --git a/src/Domain/Infrastructure/DataSource/User/DTO/User.php b/src/Domain/Infrastructure/DataSource/User/DTO/User.php index ef3cb39..1c14fd3 100644 --- a/src/Domain/Infrastructure/DataSource/User/DTO/User.php +++ b/src/Domain/Infrastructure/DataSource/User/DTO/User.php @@ -21,26 +21,26 @@ /** * @phpstan-import-type TUser from UserTypes */ -final class User +final readonly class User { public function __construct( - public readonly int $id, - public readonly int $status, - public readonly string $email, - public readonly string $password, - public readonly ?string $namePrefix, - public readonly ?string $nameFirst, - public readonly ?string $nameMiddle, - public readonly ?string $nameLast, - public readonly ?string $nameSuffix, - public readonly ?string $issuer, - public readonly ?string $tokenPassword, - public readonly ?string $tokenId, - public readonly ?string $preferences, - public readonly ?string $createdDate, - public readonly ?int $createdUserId, - public readonly ?string $updatedDate, - public readonly ?int $updatedUserId, + public int $id, + public int $status, + public string $email, + public string $password, + public ?string $namePrefix, + public ?string $nameFirst, + public ?string $nameMiddle, + public ?string $nameLast, + public ?string $nameSuffix, + public ?string $issuer, + public ?string $tokenPassword, + public ?string $tokenId, + public ?string $preferences, + public ?string $createdDate, + public ?int $createdUserId, + public ?string $updatedDate, + public ?int $updatedUserId, ) { } diff --git a/src/Domain/Infrastructure/DataSource/User/Facades/UserFacade.php b/src/Domain/Infrastructure/DataSource/User/Facades/UserFacade.php deleted file mode 100644 index 541a719..0000000 --- a/src/Domain/Infrastructure/DataSource/User/Facades/UserFacade.php +++ /dev/null @@ -1,351 +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\Domain\Infrastructure\DataSource\User\Facades; - -use PDOException; -use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\ADR\Payload; -use Phalcon\Api\Domain\Infrastructure\Constants\Dates; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\MapperInterface; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; -use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; -use Phalcon\Api\Domain\Infrastructure\Encryption\Security; -use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; - -use function array_filter; - -/** - * Orchestration for workflow - * - * - Sanitization - * - DTO creation - * - Validation - * - Pre-operation checks (when necessary) - * - Repository operation - * - * @phpstan-import-type TUserInput from InputTypes - * @phpstan-import-type TUserDomainToDbRecord from UserTypes - * @phpstan-import-type TUserDbRecordOptional from UserTypes - */ -final class UserFacade -{ - /** - * @param SanitizerInterface $sanitizer - * @param ValidatorInterface $validator - * @param MapperInterface $mapper - * @param UserRepositoryInterface $repository - * @param Security $security - */ - public function __construct( - private readonly SanitizerInterface $sanitizer, - private readonly ValidatorInterface $validator, - private readonly MapperInterface $mapper, - private readonly UserRepositoryInterface $repository, - private readonly Security $security, - ) { - } - - /** - * Delete a user. - * - * @param TUserInput $input - * - * @return Payload - */ - public function delete(array $input): Payload - { - $dto = UserInput::new($this->sanitizer, $input); - $userId = $dto->id; - - /** - * Success - */ - if ($userId > 0) { - $rowCount = $this->repository->deleteById($userId); - - if (0 !== $rowCount) { - return Payload::deleted( - [ - 'Record deleted successfully [#' . $userId . '].', - ], - ); - } - } - - /** - * 404 - */ - return Payload::notFound(); - } - - /** - * Get a user. - * - * @param TUserInput $input - * - * @return Payload - */ - public function get(array $input): Payload - { - $dto = UserInput::new($this->sanitizer, $input); - $userId = $dto->id; - - /** - * Success - */ - if ($userId > 0) { - $user = $this->repository->findById($userId); - - if (null !== $user) { - return Payload::success([$user->id => $user->toArray()]); - } - } - - /** - * 404 - */ - return Payload::notFound(); - } - - /** - * Create a user. - * - * @param TUserInput $input - * - * @return Payload - */ - public function insert(array $input): Payload - { - $dto = UserInput::new($this->sanitizer, $input); - - $validation = $this->validator->validate($dto); - if (!$validation->isValid()) { - return Payload::invalid($validation->getErrors()); - } - - /** - * Array for inserting - */ - $user = $this->mapper->db($dto); - - /** - * Pre-insert checks and manipulations - */ - $user = $this->preInsert($user); - - /** - * Insert the record - */ - try { - $userId = $this->repository->insert($user); - } catch (PDOException $ex) { - /** - * @todo send generic response and log the error - */ - return $this->getErrorPayload( - HttpCodesEnum::AppCannotCreateDatabaseRecord, - $ex->getMessage() - ); - } - - if ($userId < 1) { - return $this->getErrorPayload( - HttpCodesEnum::AppCannotCreateDatabaseRecord, - 'No id returned' - ); - } - - /** - * Get the user from the database - */ - /** @var User $domainUser */ - $domainUser = $this->repository->findById($userId); - - /** - * Return the user back - */ - return Payload::created([$domainUser->id => $domainUser->toArray()]); - } - - /** - * Create a user. - * - * @param TUserInput $input - * - * @return Payload - */ - public function update(array $input): Payload - { - $dto = UserInput::new($this->sanitizer, $input); - - $validation = $this->validator->validate($dto); - if (!$validation->isValid()) { - return Payload::invalid($validation->getErrors()); - } - - /** - * Check if the user exists, If not, return an error - */ - /** @var int $userId */ - $userId = $dto->id; - $domainUser = $this->repository->findById($userId); - - if (null === $domainUser) { - return Payload::notFound(); - } - - /** - * Array for updating - */ - $user = $this->mapper->db($dto); - - /** - * Pre-update checks and manipulations - */ - $user = $this->preUpdate($user); - - /** - * Update the record - */ - try { - $userId = $this->repository->update($userId, $user); - } catch (PDOException $ex) { - /** - * @todo send generic response and log the error - */ - return $this->getErrorPayload( - HttpCodesEnum::AppCannotUpdateDatabaseRecord, - $ex->getMessage() - ); - } - - if ($userId < 1) { - return $this->getErrorPayload( - HttpCodesEnum::AppCannotUpdateDatabaseRecord, - 'No id returned' - ); - } - - /** - * Get the user from the database - */ - /** @var User $domainUser */ - $domainUser = $this->repository->findById($userId); - - /** - * Return the user back - */ - return Payload::updated([$domainUser->id => $domainUser->toArray()]); - } - - /** - * @param TUserDbRecordOptional $row - * - * @return TUserDbRecordOptional - */ - private function cleanupFields(array $row): array - { - unset($row['usr_id']); - - return array_filter( - $row, - static fn($v) => $v !== null && $v !== '' - ); - } - - /** - * @param HttpCodesEnum $item - * @param string $message - * - * @return Payload - */ - private function getErrorPayload( - HttpCodesEnum $item, - string $message - ): Payload { - return Payload::error([[$item->text() . $message]]); - } - - /** - * @param TUserDomainToDbRecord $input - * - * @return TUserDbRecordOptional - */ - private function preInsert(array $input): array - { - $result = $this->processPassword($input); - $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); - - /** - * Set the created/updated dates if need be - */ - if (true === empty($result['usr_created_date'])) { - $result['usr_created_date'] = $now; - } - if (true === empty($result['usr_updated_date'])) { - $result['usr_updated_date'] = $now; - } - - /** @var TUserDbRecordOptional $result */ - return $this->cleanupFields($result); - } - - /** - * @param TUserDomainToDbRecord $input - * - * @return TUserDbRecordOptional - */ - private function preUpdate(array $input): array - { - $result = $this->processPassword($input); - $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); - - /** - * Set updated date to now if it has not been set - */ - if (true === empty($result['usr_updated_date'])) { - $result['usr_updated_date'] = $now; - } - - /** - * Remove createdDate and createdUserId - cannot be changed. This - * needs to be here because we don't want to touch those fields. - */ - unset($result['usr_created_date'], $result['usr_created_usr_id']); - - - return $this->cleanupFields($result); - } - - /** - * @param TUserDomainToDbRecord $input - * - * @return TUserDomainToDbRecord - */ - private function processPassword(array $input): array - { - if (null !== $input['usr_password']) { - $plain = $input['usr_password']; - $hashed = $this->security->hash($plain); - - $input['usr_password'] = $hashed; - } - - return $input; - } -} diff --git a/src/Domain/Infrastructure/DataSource/User/Mappers/UserMapper.php b/src/Domain/Infrastructure/DataSource/User/Mapper/UserMapper.php similarity index 91% rename from src/Domain/Infrastructure/DataSource/User/Mappers/UserMapper.php rename to src/Domain/Infrastructure/DataSource/User/Mapper/UserMapper.php index cc6b0c6..70866ff 100644 --- a/src/Domain/Infrastructure/DataSource/User/Mappers/UserMapper.php +++ b/src/Domain/Infrastructure/DataSource/User/Mapper/UserMapper.php @@ -11,11 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\MapperInterface; +use Phalcon\Api\Domain\Application\User\Command\UserPutCommand; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interface\MapperInterface; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; /** @@ -30,8 +31,9 @@ final class UserMapper implements MapperInterface * * @return TUserDomainToDbRecord */ - public function db(UserInput $user): array + public function db(CommandInterface $user): array { + /** @var UserPutCommand $user */ return [ 'usr_id' => $user->id, 'usr_status_flag' => $user->status, diff --git a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php b/src/Domain/Infrastructure/DataSource/User/Repository/UserRepository.php similarity index 97% rename from src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php rename to src/Domain/Infrastructure/DataSource/User/Repository/UserRepository.php index 7052084..ae62a64 100644 --- a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php +++ b/src/Domain/Infrastructure/DataSource/User/Repository/UserRepository.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository; use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractRepository; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\MapperInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interface\MapperInterface; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; use Phalcon\Api\Domain\Infrastructure\Enums\Common\FlagsEnum; diff --git a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php b/src/Domain/Infrastructure/DataSource/User/Repository/UserRepositoryInterface.php similarity index 99% rename from src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php rename to src/Domain/Infrastructure/DataSource/User/Repository/UserRepositoryInterface.php index a8a1572..15ef7ba 100644 --- a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php +++ b/src/Domain/Infrastructure/DataSource/User/Repository/UserRepositoryInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; diff --git a/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php b/src/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizer.php similarity index 76% rename from src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php rename to src/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizer.php index f158eb4..db9c45b 100644 --- a/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php +++ b/src/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizer.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizer; use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractSanitizer; -use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers\UserSanitizersEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizer\UserSanitizerEnum; final class UserSanitizer extends AbstractSanitizer { - protected string $enum = UserSanitizersEnum::class; + protected string $enum = UserSanitizerEnum::class; } diff --git a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php b/src/Domain/Infrastructure/DataSource/User/Validator/UserValidator.php similarity index 87% rename from src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php rename to src/Domain/Infrastructure/DataSource/User/Validator/UserValidator.php index 729aee7..a22c578 100644 --- a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php +++ b/src/Domain/Infrastructure/DataSource/User/Validator/UserValidator.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validator; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Infrastructure\Enums\Validators\UserInsertEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validator\UserInsertEnum; final class UserValidator extends AbstractValidator { diff --git a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php b/src/Domain/Infrastructure/DataSource/User/Validator/UserValidatorUpdate.php similarity index 87% rename from src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php rename to src/Domain/Infrastructure/DataSource/User/Validator/UserValidatorUpdate.php index 291eea1..4bded48 100644 --- a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php +++ b/src/Domain/Infrastructure/DataSource/User/Validator/UserValidatorUpdate.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validator; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Infrastructure\Enums\Validators\UserUpdateEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validator\UserUpdateEnum; final class UserValidatorUpdate extends AbstractValidator { diff --git a/src/Domain/Infrastructure/DataSource/Validation/Result.php b/src/Domain/Infrastructure/DataSource/Validation/Result.php index 064d357..f0131cf 100644 --- a/src/Domain/Infrastructure/DataSource/Validation/Result.php +++ b/src/Domain/Infrastructure/DataSource/Validation/Result.php @@ -26,7 +26,7 @@ final class Result * @param TResultMeta $meta */ public function __construct( - private array $errors = [], + private readonly array $errors = [], private array $meta = [], ) { } diff --git a/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php index 32ab547..42c805c 100644 --- a/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php +++ b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php @@ -13,7 +13,20 @@ namespace Phalcon\Api\Domain\Infrastructure\Enums\Http; -use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLoginPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLogoutPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthRefreshPostService; +use Phalcon\Api\Domain\Application\User\Service\UserDeleteService; +use Phalcon\Api\Domain\Application\User\Service\UserGetService; +use Phalcon\Api\Domain\Application\User\Service\UserPostService; +use Phalcon\Api\Domain\Application\User\Service\UserPutService; +use Phalcon\Api\Domain\Infrastructure\Middleware\HealthMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\NotFoundMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenClaimsMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenPresenceMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenRevokedMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenStructureMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenUserMiddleware; use function str_replace; @@ -22,16 +35,15 @@ */ enum RoutesEnum: int { + /** + * Methods + */ + public const DELETE = 'delete'; /** * Events */ public const EVENT_BEFORE = 'before'; public const EVENT_FINISH = 'finish'; - - /** - * Methods - */ - public const DELETE = 'delete'; public const GET = 'get'; public const POST = 'post'; public const PUT = 'put'; @@ -75,13 +87,13 @@ public function method(): string public static function middleware(): array { return [ - Container::MIDDLEWARE_NOT_FOUND => self::EVENT_BEFORE, - Container::MIDDLEWARE_HEALTH => self::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_PRESENCE => self::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_STRUCTURE => self::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_USER => self::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_CLAIMS => self::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_REVOKED => self::EVENT_BEFORE, + NotFoundMiddleware::class => self::EVENT_BEFORE, + HealthMiddleware::class => self::EVENT_BEFORE, + ValidateTokenPresenceMiddleware::class => self::EVENT_BEFORE, + ValidateTokenStructureMiddleware::class => self::EVENT_BEFORE, + ValidateTokenUserMiddleware::class => self::EVENT_BEFORE, + ValidateTokenClaimsMiddleware::class => self::EVENT_BEFORE, + ValidateTokenRevokedMiddleware::class => self::EVENT_BEFORE, ]; } @@ -106,13 +118,13 @@ public function prefix(): string public function service(): string { return match ($this) { - self::authLoginPost => Container::AUTH_LOGIN_POST_SERVICE, - self::authLogoutPost => Container::AUTH_LOGOUT_POST_SERVICE, - self::authRefreshPost => Container::AUTH_REFRESH_POST_SERVICE, - self::userDelete => Container::USER_DELETE_SERVICE, - self::userGet => Container::USER_GET_SERVICE, - self::userPost => Container::USER_POST_SERVICE, - self::userPut => Container::USER_PUT_SERVICE, + self::authLoginPost => AuthLoginPostService::class, + self::authLogoutPost => AuthLogoutPostService::class, + self::authRefreshPost => AuthRefreshPostService::class, + self::userDelete => UserDeleteService::class, + self::userGet => UserGetService::class, + self::userPost => UserPostService::class, + self::userPut => UserPutService::class, }; } diff --git a/src/Domain/Infrastructure/Enums/Sanitizers/AuthSanitizersEnum.php b/src/Domain/Infrastructure/Enums/Sanitizer/AuthSanitizerEnum.php similarity index 82% rename from src/Domain/Infrastructure/Enums/Sanitizers/AuthSanitizersEnum.php rename to src/Domain/Infrastructure/Enums/Sanitizer/AuthSanitizerEnum.php index 2018e0f..5baf791 100644 --- a/src/Domain/Infrastructure/Enums/Sanitizers/AuthSanitizersEnum.php +++ b/src/Domain/Infrastructure/Enums/Sanitizer/AuthSanitizerEnum.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Sanitizer; use Phalcon\Filter\Filter; -enum AuthSanitizersEnum implements SanitizersEnumInterface +enum AuthSanitizerEnum implements SanitizersEnumInterface { case email; case password; diff --git a/src/Domain/Infrastructure/Enums/Sanitizers/UserSanitizersEnum.php b/src/Domain/Infrastructure/Enums/Sanitizer/UserSanitizerEnum.php similarity index 91% rename from src/Domain/Infrastructure/Enums/Sanitizers/UserSanitizersEnum.php rename to src/Domain/Infrastructure/Enums/Sanitizer/UserSanitizerEnum.php index ff61d68..ee3eea5 100644 --- a/src/Domain/Infrastructure/Enums/Sanitizers/UserSanitizersEnum.php +++ b/src/Domain/Infrastructure/Enums/Sanitizer/UserSanitizerEnum.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Sanitizer; use Phalcon\Filter\Filter; -enum UserSanitizersEnum implements SanitizersEnumInterface +enum UserSanitizerEnum implements SanitizersEnumInterface { case id; case status; diff --git a/src/Domain/Infrastructure/Enums/Validators/UserUpdateEnum.php b/src/Domain/Infrastructure/Enums/Validator/UserUpdateEnum.php similarity index 69% rename from src/Domain/Infrastructure/Enums/Validators/UserUpdateEnum.php rename to src/Domain/Infrastructure/Enums/Validator/UserUpdateEnum.php index 31ee31f..2ce9983 100644 --- a/src/Domain/Infrastructure/Enums/Validators/UserUpdateEnum.php +++ b/src/Domain/Infrastructure/Enums/Validator/UserUpdateEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validator; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbsInt; use Phalcon\Filter\Validation\Validator\Email; @@ -26,18 +26,25 @@ enum UserUpdateEnum implements ValidatorEnumInterface case tokenPassword; case tokenId; + public function allowEmpty(): bool + { + return match ($this) { + self::id => false, + default => true, + }; + } + public function validators(): array { return match ($this) { self::id => [ PresenceOf::class, - AbsInt::class + AbsInt::class, ], - self::email => [ - PresenceOf::class, - Email::class + self::email => [ + Email::class, ], - default => [PresenceOf::class], + default => [], }; } } diff --git a/src/Domain/Infrastructure/Providers/ErrorHandlerProvider.php b/src/Domain/Infrastructure/Providers/ErrorHandlerProvider.php index 1dd1ffa..80f42f8 100644 --- a/src/Domain/Infrastructure/Providers/ErrorHandlerProvider.php +++ b/src/Domain/Infrastructure/Providers/ErrorHandlerProvider.php @@ -32,9 +32,9 @@ class ErrorHandlerProvider implements ServiceProviderInterface public function register(DiInterface $container): void { /** @var EnvManager $env */ - $env = $container->getShared(Container::ENV); + $env = $container->getShared(EnvManager::class); /** @var Logger $logger */ - $logger = $container->getShared(Container::LOGGER); + $logger = $container->getShared(Logger::class); /** @var int $time */ $time = $container->getShared(Container::TIME); diff --git a/src/Domain/Infrastructure/Providers/RouterProvider.php b/src/Domain/Infrastructure/Providers/RouterProvider.php index 2d184ae..deede91 100644 --- a/src/Domain/Infrastructure/Providers/RouterProvider.php +++ b/src/Domain/Infrastructure/Providers/RouterProvider.php @@ -17,6 +17,7 @@ use Phalcon\Api\Domain\ADR\DomainInterface; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Enums\Http\RoutesEnum; +use Phalcon\Api\Responder\JsonResponder; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Di\DiInterface; use Phalcon\Di\ServiceProviderInterface; @@ -82,7 +83,7 @@ private function attachRoutes(Micro $application): void /** @var ResponseInterface $response */ $response = $application->getService(Container::RESPONSE); /** @var ResponderInterface $responder */ - $responder = $application->getService(Container::RESPONDER_JSON); + $responder = $application->getService(JsonResponder::class); $routes = RoutesEnum::cases(); foreach ($routes as $route) { diff --git a/tests/AbstractUnitTestCase.php b/tests/AbstractUnitTestCase.php index 381d2ee..4755dbc 100644 --- a/tests/AbstractUnitTestCase.php +++ b/tests/AbstractUnitTestCase.php @@ -89,7 +89,7 @@ public function getConnection(): Connection { if (null === $this->connection) { /** @var Connection $connection */ - $connection = $this->container->getShared(Container::CONNECTION); + $connection = $this->container->getShared(Connection::class); $this->connection = $connection; } @@ -146,7 +146,7 @@ public function getNewUserData(array $fields = []): array $faker = Factory::create(); $password = $fields['usr_password'] ?? $this->getStrongPassword(); /** @var Security $security */ - $security = $this->container->getShared(Container::SECURITY); + $security = $this->container->getShared(Security::class); $password = $security->hash($password); diff --git a/tests/Unit/Action/ActionHandlerTest.php b/tests/Unit/Action/ActionHandlerTest.php index a572428..fdeb3d5 100644 --- a/tests/Unit/Action/ActionHandlerTest.php +++ b/tests/Unit/Action/ActionHandlerTest.php @@ -15,9 +15,10 @@ use Phalcon\Api\Action\ActionHandler; use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Responder\JsonResponder; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Api\Tests\Fixtures\Domain\Services\ServiceFixture; +use Phalcon\Api\Tests\Fixtures\Domain\Application\Service\ServiceFixture; use Phalcon\Http\RequestInterface; use Phalcon\Http\ResponseInterface; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -36,7 +37,7 @@ public function testInvoke(): void /** @var ResponseInterface $response */ $response = $this->container->getShared(Container::RESPONSE); /** @var ResponderInterface $responder */ - $responder = $this->container->getShared(Container::RESPONDER_JSON); + $responder = $this->container->getShared(JsonResponder::class); $getData = [ 'key' => uniqid('key-'), diff --git a/tests/Unit/Domain/ADR/InputTest.php b/tests/Unit/Domain/ADR/InputTest.php index c82ecb3..8aa411f 100644 --- a/tests/Unit/Domain/ADR/InputTest.php +++ b/tests/Unit/Domain/ADR/InputTest.php @@ -24,16 +24,16 @@ final class InputTest extends TestCase { public function testInvoke(): void { - $faker = Factory::create(); + $faker = Factory::create(); $postData = [ 'post1' => $faker->word(), 'post2' => $faker->word(), ]; - $getData = [ + $getData = [ 'get1' => $faker->word(), 'get2' => $faker->word(), ]; - $putData = [ + $putData = [ 'put1' => $faker->word(), 'put2' => $faker->word(), ]; @@ -63,7 +63,7 @@ public function testInvoke(): void 'put1' => $putData['put1'], 'put2' => $putData['put2'], ]; - $actual = $input->__invoke($mockRequest); + $actual = $input->__invoke($mockRequest); $this->assertSame($expected, $actual); } } diff --git a/tests/Unit/Domain/ADR/PayloadTest.php b/tests/Unit/Domain/ADR/PayloadTest.php index 4ec98c7..f5e744a 100644 --- a/tests/Unit/Domain/ADR/PayloadTest.php +++ b/tests/Unit/Domain/ADR/PayloadTest.php @@ -22,126 +22,126 @@ final class PayloadTest extends AbstractUnitTestCase { public function testCreated(): void { - $data = ['id' => 1]; + $data = ['id' => 1]; $payload = Payload::created($data); $expected = Payload::class; - $actual = get_class($payload); + $actual = get_class($payload); $this->assertSame($expected, $actual); $expected = DomainStatus::CREATED; - $actual = $payload->getStatus(); + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); $expected = ['data' => $data]; - $actual = $payload->getResult(); + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } public function testDeleted(): void { - $data = ['deleted' => true]; + $data = ['deleted' => true]; $payload = Payload::deleted($data); $expected = DomainStatus::DELETED; - $actual = $payload->getStatus(); + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); $expected = ['data' => $data]; - $actual = $payload->getResult(); + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } - public function testUpdated(): void + public function testError(): void { - $data = ['updated' => true]; - $payload = Payload::updated($data); + $errors = [['something' => 'went wrong']]; + $payload = Payload::error($errors); - $expected = DomainStatus::UPDATED; - $actual = $payload->getStatus(); + $expected = DomainStatus::ERROR; + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); - $expected = ['data' => $data]; - $actual = $payload->getResult(); + $expected = ['errors' => $errors]; + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } - public function testSuccess(): void + public function testInvalid(): void { - $data = ['items' => [1, 2, 3]]; - $payload = Payload::success($data); + $errors = [['field' => 'invalid']]; + $payload = Payload::invalid($errors); - $expected = DomainStatus::SUCCESS; - $actual = $payload->getStatus(); + $expected = DomainStatus::INVALID; + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); - $expected = ['data' => $data]; - $actual = $payload->getResult(); + $expected = ['errors' => $errors]; + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } - public function testError(): void + public function testNotFound(): void { - $errors = [['something' => 'went wrong']]; - $payload = Payload::error($errors); + $payload = Payload::notFound(); - $expected = DomainStatus::ERROR; - $actual = $payload->getStatus(); + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); - $expected = ['errors' => $errors]; - $actual = $payload->getResult(); + $expected = [ + 'code' => HttpCodesEnum::NotFound->value, + 'message' => HttpCodesEnum::NotFound->text(), + 'data' => [], + 'errors' => [['Record(s) not found']], + ]; + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } - public function testInvalid(): void + public function testSuccess(): void { - $errors = [['field' => 'invalid']]; - $payload = Payload::invalid($errors); + $data = ['items' => [1, 2, 3]]; + $payload = Payload::success($data); - $expected = DomainStatus::INVALID; - $actual = $payload->getStatus(); + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); - $expected = ['errors' => $errors]; - $actual = $payload->getResult(); + $expected = ['data' => $data]; + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } public function testUnauthorized(): void { - $errors = [['reason' => 'no access']]; + $errors = [['reason' => 'no access']]; $payload = Payload::unauthorized($errors); $expected = DomainStatus::UNAUTHORIZED; - $actual = $payload->getStatus(); + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); $expected = [ 'code' => HttpCodesEnum::Unauthorized->value, 'message' => HttpCodesEnum::Unauthorized->text(), 'data' => [], - 'errors' => $errors + 'errors' => $errors, ]; - $actual = $payload->getResult(); + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } - public function testNotFound(): void + public function testUpdated(): void { - $payload = Payload::notFound(); + $data = ['updated' => true]; + $payload = Payload::updated($data); - $expected = DomainStatus::NOT_FOUND; - $actual = $payload->getStatus(); + $expected = DomainStatus::UPDATED; + $actual = $payload->getStatus(); $this->assertSame($expected, $actual); - $expected = [ - 'code' => HttpCodesEnum::NotFound->value, - 'message' => HttpCodesEnum::NotFound->text(), - 'data' => [], - 'errors' => [['Record(s) not found']] - ]; - $actual = $payload->getResult(); + $expected = ['data' => $data]; + $actual = $payload->getResult(); $this->assertSame($expected, $actual); } } diff --git a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php b/tests/Unit/Domain/Application/Auth/Service/LoginPostServiceTest.php similarity index 87% rename from tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php rename to tests/Unit/Domain/Application/Auth/Service/LoginPostServiceTest.php index 3bc5aae..9ea47ae 100644 --- a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php +++ b/tests/Unit/Domain/Application/Auth/Service/LoginPostServiceTest.php @@ -11,13 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\Auth; +namespace Phalcon\Api\Tests\Unit\Domain\Application\Auth\Service; use Faker\Factory; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLoginPostService; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Services\Auth\LoginPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -27,8 +26,8 @@ final class LoginPostServiceTest extends AbstractUnitTestCase { public function testServiceEmptyCredentials(): void { - /** @var LoginPostService $service */ - $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); + /** @var AuthLoginPostService $service */ + $service = $this->container->get(AuthLoginPostService::class); $payload = $service->__invoke([]); @@ -46,8 +45,8 @@ public function testServiceEmptyCredentials(): void public function testServiceWithCredentials(): void { - /** @var LoginPostService $service */ - $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); + /** @var AuthLoginPostService $service */ + $service = $this->container->get(AuthLoginPostService::class); $migration = new UsersMigration($this->getConnection()); /** @@ -113,8 +112,8 @@ public function testServiceWithCredentials(): void public function testServiceWrongCredentials(): void { $faker = Factory::create(); - /** @var LoginPostService $service */ - $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); + /** @var AuthLoginPostService $service */ + $service = $this->container->get(AuthLoginPostService::class); /** * Issue a wrong password @@ -140,8 +139,8 @@ public function testServiceWrongCredentials(): void public function testServiceWrongCredentialsForUser(): void { - /** @var LoginPostService $service */ - $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); + /** @var AuthLoginPostService $service */ + $service = $this->container->get(AuthLoginPostService::class); $migration = new UsersMigration($this->getConnection()); $password = 'password'; diff --git a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php b/tests/Unit/Domain/Application/Auth/Service/LogoutPostServiceTest.php similarity index 85% rename from tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php rename to tests/Unit/Domain/Application/Auth/Service/LogoutPostServiceTest.php index 326be2b..afddf99 100644 --- a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php +++ b/tests/Unit/Domain/Application/Auth/Service/LogoutPostServiceTest.php @@ -11,16 +11,15 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\Auth; +namespace Phalcon\Api\Tests\Unit\Domain\Application\Auth\Service; use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLoginPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLogoutPostService; use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Services\Auth\LoginPostService; -use Phalcon\Api\Domain\Services\Auth\LogoutPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Cache\Cache; @@ -33,8 +32,8 @@ final class LogoutPostServiceTest extends AbstractUnitTestCase { public function testServiceEmptyToken(): void { - /** @var LogoutPostService $service */ - $service = $this->container->get(Container::AUTH_LOGOUT_POST_SERVICE); + /** @var AuthLogoutPostService $service */ + $service = $this->container->get(AuthLogoutPostService::class); $payload = $service->__invoke([]); @@ -53,7 +52,7 @@ public function testServiceEmptyToken(): void public function testServiceInvalidToken(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $user = $this->getNewUserData(); $user['usr_id'] = 1; $domainUser = $userMapper->domain($user); @@ -108,10 +107,10 @@ public function testServiceInvalidToken(): void /** * Replace the service with the mocked one */ - $this->container->set(Container::JWT_TOKEN, $mockJWT); + $this->container->set(JWTToken::class, $mockJWT); - /** @var LogoutPostService $service */ - $service = $this->container->get(Container::AUTH_LOGOUT_POST_SERVICE); + /** @var AuthLogoutPostService $service */ + $service = $this->container->get(AuthLogoutPostService::class); $payload = $service->__invoke(['token' => '1234']); @@ -171,9 +170,9 @@ public function testServiceNotRefreshToken(): void /** * Replace the service with the mocked one */ - $this->container->set(Container::JWT_TOKEN, $mockJWT); - /** @var LogoutPostService $service */ - $logoutService = $this->container->get(Container::AUTH_LOGOUT_POST_SERVICE); + $this->container->set(JWTToken::class, $mockJWT); + /** @var AuthLogoutPostService $service */ + $logoutService = $this->container->get(AuthLogoutPostService::class); /** * Logout now @@ -197,13 +196,13 @@ public function testServiceNotRefreshToken(): void public function testServiceSuccess(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); - /** @var LogoutPostService $service */ - $logoutService = $this->container->get(Container::AUTH_LOGOUT_POST_SERVICE); + $userMapper = $this->container->get(UserMapper::class); + /** @var AuthLogoutPostService $service */ + $logoutService = $this->container->get(AuthLogoutPostService::class); /** @var Cache $cache */ - $cache = $this->container->getShared(Container::CACHE); - /** @var LoginPostService $service */ - $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); + $cache = $this->container->getShared(Cache::class); + /** @var AuthLoginPostService $service */ + $service = $this->container->get(AuthLoginPostService::class); $migration = new UsersMigration($this->getConnection()); /** @@ -283,7 +282,7 @@ public function testServiceSuccess(): void public function testServiceWrongUser(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $domainUser = $userMapper->domain([]); /** @@ -331,10 +330,10 @@ public function testServiceWrongUser(): void /** * Replace the service with the mocked one */ - $this->container->set(Container::JWT_TOKEN, $mockJWT); + $this->container->set(JWTToken::class, $mockJWT); - /** @var LogoutPostService $service */ - $logoutService = $this->container->get(Container::AUTH_LOGOUT_POST_SERVICE); + /** @var AuthLogoutPostService $service */ + $logoutService = $this->container->get(AuthLogoutPostService::class); /** * Logout now diff --git a/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php b/tests/Unit/Domain/Application/Auth/Service/RefreshPostServiceTest.php similarity index 85% rename from tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php rename to tests/Unit/Domain/Application/Auth/Service/RefreshPostServiceTest.php index fdf5937..6c13192 100644 --- a/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php +++ b/tests/Unit/Domain/Application/Auth/Service/RefreshPostServiceTest.php @@ -11,15 +11,14 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\Auth; +namespace Phalcon\Api\Tests\Unit\Domain\Application\Auth\Service; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLoginPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthRefreshPostService; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Services\Auth\LoginPostService; -use Phalcon\Api\Domain\Services\Auth\RefreshPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Encryption\Security\JWT\Token\Item; @@ -31,8 +30,8 @@ final class RefreshPostServiceTest extends AbstractUnitTestCase { public function testServiceEmptyToken(): void { - /** @var RefreshPostService $service */ - $service = $this->container->get(Container::AUTH_REFRESH_POST_SERVICE); + /** @var AuthRefreshPostService $service */ + $service = $this->container->get(AuthRefreshPostService::class); $payload = $service->__invoke([]); @@ -51,7 +50,7 @@ public function testServiceEmptyToken(): void public function testServiceInvalidToken(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $user = $this->getNewUserData(); $user['usr_id'] = 1; $domainUser = $userMapper->domain($user); @@ -106,10 +105,10 @@ public function testServiceInvalidToken(): void /** * Replace the service with the mocked one */ - $this->container->set(Container::JWT_TOKEN, $mockJWT); + $this->container->set(JWTToken::class, $mockJWT); - /** @var RefreshPostService $service */ - $service = $this->container->get(Container::AUTH_REFRESH_POST_SERVICE); + /** @var AuthRefreshPostService $service */ + $service = $this->container->get(AuthRefreshPostService::class); $payload = $service->__invoke(['token' => '1234']); @@ -169,10 +168,10 @@ public function testServiceNotRefreshToken(): void /** * Replace the service with the mocked one */ - $this->container->set(Container::JWT_TOKEN, $mockJWT); + $this->container->set(JWTToken::class, $mockJWT); - /** @var RefreshPostService $service */ - $service = $this->container->get(Container::AUTH_REFRESH_POST_SERVICE); + /** @var AuthRefreshPostService $service */ + $service = $this->container->get(AuthRefreshPostService::class); $payload = $service->__invoke(['token' => '1234']); @@ -190,10 +189,10 @@ public function testServiceNotRefreshToken(): void public function testServiceWithCredentials(): void { - /** @var LoginPostService $service */ - $loginService = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); - /** @var RefreshPostService $service */ - $service = $this->container->get(Container::AUTH_REFRESH_POST_SERVICE); + /** @var AuthLoginPostService $service */ + $loginService = $this->container->get(AuthLoginPostService::class); + /** @var AuthRefreshPostService $service */ + $service = $this->container->get(AuthRefreshPostService::class); $migration = new UsersMigration($this->getConnection()); /** @@ -284,10 +283,10 @@ public function testServiceWrongUser(): void /** * Replace the service with the mocked one */ - $this->container->set(Container::JWT_TOKEN, $mockJWT); + $this->container->set(JWTToken::class, $mockJWT); - /** @var RefreshPostService $service */ - $service = $this->container->get(Container::AUTH_REFRESH_POST_SERVICE); + /** @var AuthRefreshPostService $service */ + $service = $this->container->get(AuthRefreshPostService::class); $payload = $service->__invoke(['token' => '1234']); diff --git a/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php b/tests/Unit/Domain/Application/User/Service/UserServiceDeleteTest.php similarity index 87% rename from tests/Unit/Domain/Services/User/UserServiceDeleteTest.php rename to tests/Unit/Domain/Application/User/Service/UserServiceDeleteTest.php index dde46b4..fdbe759 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServiceDeleteTest.php @@ -11,22 +11,19 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\User; +namespace Phalcon\Api\Tests\Unit\Domain\Application\User\Service; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Services\User\UserDeleteService; +use Phalcon\Api\Domain\Application\User\Service\UserDeleteService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; -use PHPUnit\Framework\Attributes\BackupGlobals; -#[BackupGlobals(true)] final class UserServiceDeleteTest extends AbstractUnitTestCase { public function testServiceWithUserId(): void { /** @var UserDeleteService $service */ - $service = $this->container->get(Container::USER_DELETE_SERVICE); + $service = $this->container->get(UserDeleteService::class); /** * We need to ask for a user to be deleted with an ID that does not @@ -84,7 +81,7 @@ public function testServiceWithUserId(): void public function testServiceZeroUserId(): void { /** @var UserDeleteService $service */ - $service = $this->container->get(Container::USER_DELETE_SERVICE); + $service = $this->container->get(UserDeleteService::class); $payload = $service->__invoke( [ diff --git a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php b/tests/Unit/Domain/Application/User/Service/UserServiceDispatchTest.php similarity index 88% rename from tests/Unit/Domain/Services/User/UserServiceDispatchTest.php rename to tests/Unit/Domain/Application/User/Service/UserServiceDispatchTest.php index 128da7b..78798f3 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServiceDispatchTest.php @@ -11,13 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\User; +namespace Phalcon\Api\Tests\Unit\Domain\Application\User\Service; use DateTimeImmutable; use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; use Phalcon\Api\Domain\Infrastructure\Constants\Dates; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; use Phalcon\Api\Domain\Infrastructure\Enums\Http\RoutesEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Tests\AbstractUnitTestCase; @@ -31,11 +30,11 @@ final class UserServiceDispatchTest extends AbstractUnitTestCase public function testDispatchGet(): void { /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); /** @var Cache $cache */ - $cache = $this->container->getShared(Container::CACHE); + $cache = $this->container->getShared(Cache::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $migration = new UsersMigration($this->getConnection()); $dbUser = $this->getNewUser($migration); diff --git a/tests/Unit/Domain/Services/User/UserServiceGetTest.php b/tests/Unit/Domain/Application/User/Service/UserServiceGetTest.php similarity index 91% rename from tests/Unit/Domain/Services/User/UserServiceGetTest.php rename to tests/Unit/Domain/Application/User/Service/UserServiceGetTest.php index 0bcbd48..95a5cde 100644 --- a/tests/Unit/Domain/Services/User/UserServiceGetTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServiceGetTest.php @@ -11,22 +11,19 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\User; +namespace Phalcon\Api\Tests\Unit\Domain\Application\User\Service; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Services\User\UserGetService; +use Phalcon\Api\Domain\Application\User\Service\UserGetService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; -use PHPUnit\Framework\Attributes\BackupGlobals; -#[BackupGlobals(true)] final class UserServiceGetTest extends AbstractUnitTestCase { public function testServiceEmptyUserId(): void { /** @var UserGetService $service */ - $service = $this->container->get(Container::USER_GET_SERVICE); + $service = $this->container->get(UserGetService::class); $payload = $service->__invoke([]); @@ -47,7 +44,7 @@ public function testServiceEmptyUserId(): void public function testServiceWithUserId(): void { /** @var UserGetService $service */ - $service = $this->container->get(Container::USER_GET_SERVICE); + $service = $this->container->get(UserGetService::class); $migration = new UsersMigration($this->getConnection()); $dbUser = $this->getNewUser($migration); @@ -142,7 +139,7 @@ public function testServiceWithUserId(): void public function testServiceWrongUserId(): void { /** @var UserGetService $service */ - $service = $this->container->get(Container::USER_GET_SERVICE); + $service = $this->container->get(UserGetService::class); $payload = $service->__invoke( [ diff --git a/tests/Unit/Domain/Services/User/UserServicePostTest.php b/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php similarity index 81% rename from tests/Unit/Domain/Services/User/UserServicePostTest.php rename to tests/Unit/Domain/Application/User/Service/UserServicePostTest.php index d3a92c6..2bfa6bc 100644 --- a/tests/Unit/Domain/Services/User/UserServicePostTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php @@ -11,21 +11,19 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\User; +namespace Phalcon\Api\Tests\Unit\Domain\Application\User\Service; use DateTimeImmutable; use PayloadInterop\DomainStatus; use PDOException; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; -use Phalcon\Api\Domain\Services\User\UserPostService; +use Phalcon\Api\Domain\Application\User\Service\UserPostService; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; use Phalcon\Api\Tests\AbstractUnitTestCase; -use PHPUnit\Framework\Attributes\BackupGlobals; +use Phalcon\Support\Registry; use function htmlspecialchars; -#[BackupGlobals(true)] final class UserServicePostTest extends AbstractUnitTestCase { public function testServiceFailureNoIdReturned(): void @@ -42,12 +40,14 @@ public function testServiceFailureNoIdReturned(): void ; $userRepository->method('insert')->willReturn(0); - $this->container->setShared(Container::USER_REPOSITORY, $userRepository); + $this->container->setShared(UserRepository::class, $userRepository); /** @var UserPostService $service */ - $service = $this->container->get(Container::USER_POST_SERVICE); + $service = $this->container->get(UserPostService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -56,6 +56,12 @@ public function testServiceFailureNoIdReturned(): void * $userData is a db record. We need a domain object here */ $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + $domainData = $domainUser->toArray(); $payload = $service->__invoke($domainData); @@ -91,12 +97,14 @@ public function testServiceFailurePdoError(): void ->willThrowException(new PDOException('abcde')) ; - $this->container->setShared(Container::USER_REPOSITORY, $userRepository); + $this->container->setShared(UserRepository::class, $userRepository); /** @var UserPostService $service */ - $service = $this->container->get(Container::USER_POST_SERVICE); + $service = $this->container->get(UserPostService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -105,6 +113,12 @@ public function testServiceFailurePdoError(): void * $userData is a db record. We need a domain object here */ $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + $domainData = $domainUser->toArray(); $payload = $service->__invoke($domainData); @@ -126,9 +140,9 @@ public function testServiceFailurePdoError(): void public function testServiceFailureValidation(): void { /** @var UserPostService $service */ - $service = $this->container->get(Container::USER_POST_SERVICE); + $service = $this->container->get(UserPostService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); $userData = $this->getNewUserData(); @@ -172,18 +186,24 @@ public function testServiceFailureValidation(): void public function testServiceSuccess(): void { /** @var UserPostService $service */ - $service = $this->container->get(Container::USER_POST_SERVICE); + $service = $this->container->get(UserPostService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); - $userData = $this->getNewUserData(); - $userData['usr_created_usr_id'] = 4; - $userData['usr_updated_usr_id'] = 5; + $userData = $this->getNewUserData(); /** * $userData is a db record. We need a domain object here */ $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + $domainData = $domainUser->toArray(); $payload = $service->__invoke($domainData); @@ -253,7 +273,7 @@ public function testServiceSuccess(): void $actual = $data['createdDate']; $this->assertSame($expected, $actual); - $expected = 4; + $expected = $domainUser->id; $actual = $data['createdUserId']; $this->assertSame($expected, $actual); @@ -261,7 +281,7 @@ public function testServiceSuccess(): void $actual = $data['updatedDate']; $this->assertSame($expected, $actual); - $expected = 5; + $expected = $domainUser->id; $actual = $data['updatedUserId']; $this->assertSame($expected, $actual); } @@ -271,9 +291,11 @@ public function testServiceSuccessEmptyDates(): void $now = new DateTimeImmutable(); $today = $now->format('Y-m-d'); /** @var UserPostService $service */ - $service = $this->container->get(Container::USER_POST_SERVICE); + $service = $this->container->get(UserPostService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); $userData = $this->getNewUserData(); unset( @@ -285,6 +307,12 @@ public function testServiceSuccessEmptyDates(): void * $userData is a db record. We need a domain object here */ $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + $domainData = $domainUser->toArray(); $payload = $service->__invoke($domainData); diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Application/User/Service/UserServicePutTest.php similarity index 79% rename from tests/Unit/Domain/Services/User/UserServicePutTest.php rename to tests/Unit/Domain/Application/User/Service/UserServicePutTest.php index 799f9a3..aea8afb 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServicePutTest.php @@ -11,27 +11,27 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Services\User; +namespace Phalcon\Api\Tests\Unit\Domain\Application\User\Service; use DateTimeImmutable; use PayloadInterop\DomainStatus; use PDOException; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; -use Phalcon\Api\Domain\Services\User\UserPutService; +use Phalcon\Api\Domain\Application\User\Service\UserPutService; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; -use PHPUnit\Framework\Attributes\BackupGlobals; +use Phalcon\Support\Registry; -#[BackupGlobals(true)] final class UserServicePutTest extends AbstractUnitTestCase { public function testServiceFailureNoIdReturned(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -51,10 +51,10 @@ public function testServiceFailureNoIdReturned(): void $userRepository->method('update')->willReturn(0); $userRepository->method('findById')->willReturn($findByUser); - $this->container->setShared(Container::USER_REPOSITORY, $userRepository); + $this->container->setShared(UserRepository::class, $userRepository); /** @var UserPutService $service */ - $service = $this->container->get(Container::USER_PUT_SERVICE); + $service = $this->container->get(UserPutService::class); /** * Update user @@ -63,6 +63,12 @@ public function testServiceFailureNoIdReturned(): void $userData['usr_id'] = 1; $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + $updateUser = $updateUser->toArray(); $payload = $service->__invoke($updateUser); @@ -84,8 +90,10 @@ public function testServiceFailureNoIdReturned(): void public function testServiceFailurePdoError(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -108,10 +116,10 @@ public function testServiceFailurePdoError(): void ->willThrowException(new PDOException('abcde')) ; - $this->container->setShared(Container::USER_REPOSITORY, $userRepository); + $this->container->setShared(UserRepository::class, $userRepository); /** @var UserPutService $service */ - $service = $this->container->get(Container::USER_PUT_SERVICE); + $service = $this->container->get(UserPutService::class); $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -120,6 +128,12 @@ public function testServiceFailurePdoError(): void * $userData is a db record. We need a domain object here */ $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + $updateUser = $updateUser->toArray(); $payload = $service->__invoke($updateUser); @@ -141,13 +155,13 @@ public function testServiceFailurePdoError(): void public function testServiceFailureRecordNotFound(): void { /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + $userData = $this->getNewUserData(); $userData['usr_id'] = 1; - $findByUser = $userMapper->domain($userData); - $userRepository = $this ->getMockBuilder(UserRepository::class) ->disableOriginalConstructor() @@ -160,10 +174,10 @@ public function testServiceFailureRecordNotFound(): void ; $userRepository->method('findById')->willReturn(null); - $this->container->setShared(Container::USER_REPOSITORY, $userRepository); + $this->container->setShared(UserRepository::class, $userRepository); /** @var UserPutService $service */ - $service = $this->container->get(Container::USER_PUT_SERVICE); + $service = $this->container->get(UserPutService::class); /** * Update user @@ -172,6 +186,12 @@ public function testServiceFailureRecordNotFound(): void $userData['usr_id'] = 1; $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + $updateUser = $updateUser->toArray(); $payload = $service->__invoke($updateUser); @@ -193,24 +213,26 @@ public function testServiceFailureRecordNotFound(): void public function testServiceFailureValidation(): void { /** @var UserPutService $service */ - $service = $this->container->get(Container::USER_PUT_SERVICE); + $service = $this->container->get(UserPutService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + $userData = $this->getNewUserData(); - unset( - $userData['usr_id'], - $userData['usr_email'], - $userData['usr_password'], - $userData['usr_issuer'], - $userData['usr_token_password'], - $userData['usr_token_id'] - ); + $userData['usr_email'] = 'wrong-email'; + unset($userData['usr_id']); /** * $userData is a db record. We need a domain object here */ $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + $updateUser = $updateUser->toArray(); $payload = $service->__invoke($updateUser); @@ -226,12 +248,7 @@ public function testServiceFailureValidation(): void $expected = [ ['Field id is not a valid absolute integer and greater than 0'], - ['Field email is required'], ['Field email must be an email address'], - ['Field password is required'], - ['Field issuer is required'], - ['Field tokenPassword is required'], - ['Field tokenId is required'], ]; $actual = $errors; $this->assertSame($expected, $actual); @@ -240,9 +257,11 @@ public function testServiceFailureValidation(): void public function testServiceSuccess(): void { /** @var UserPutService $service */ - $service = $this->container->get(Container::USER_PUT_SERVICE); + $service = $this->container->get(UserPutService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); $migration = new UsersMigration($this->getConnection()); $dbUser = $this->getNewUser($migration); @@ -255,12 +274,17 @@ public function testServiceSuccess(): void $userData['usr_password'] = $this->getStrongPassword(); $userData['usr_created_usr_id'] = 4; - $userData['usr_updated_usr_id'] = 5; /** * $userData is a db record. We need a domain object here */ $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + $domainData = $domainUser->toArray(); $payload = $service->__invoke($domainData); @@ -343,7 +367,7 @@ public function testServiceSuccess(): void $actual = $data['updatedDate']; $this->assertSame($expected, $actual); - $expected = 5; + $expected = $domainUser->id; $actual = $data['updatedUserId']; $this->assertSame($expected, $actual); } @@ -353,9 +377,11 @@ public function testServiceSuccessEmptyDates(): void $now = new DateTimeImmutable(); $today = $now->format('Y-m-d'); /** @var UserPutService $service */ - $service = $this->container->get(Container::USER_PUT_SERVICE); + $service = $this->container->get(UserPutService::class); /** @var UserMapper $userMapper */ - $userMapper = $this->container->get(Container::USER_MAPPER); + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); $migration = new UsersMigration($this->getConnection()); $dbUser = $this->getNewUser($migration); @@ -376,6 +402,12 @@ public function testServiceSuccessEmptyDates(): void * $userData is a db record. We need a domain object here */ $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + $domainData = $domainUser->toArray(); $payload = $service->__invoke($domainData); @@ -458,7 +490,7 @@ public function testServiceSuccessEmptyDates(): void $actual = $data['updatedDate']; $this->assertStringContainsString($today, $actual); - $expected = $dbUser['usr_updated_usr_id']; + $expected = $domainUser->id; $actual = $data['updatedUserId']; $this->assertSame($expected, $actual); } diff --git a/tests/Unit/Domain/Infrastructure/Constants/CacheTest.php b/tests/Unit/Domain/Infrastructure/Constants/CacheTest.php index 30d27b6..e08a0bd 100644 --- a/tests/Unit/Domain/Infrastructure/Constants/CacheTest.php +++ b/tests/Unit/Domain/Infrastructure/Constants/CacheTest.php @@ -44,12 +44,12 @@ public function testConstants(): void $actual = Cache::CACHE_TOKEN_EXPIRY; $this->assertSame($expected, $actual); - $token = uniqid('tok-'); + $token = uniqid('tok-'); $shaToken = sha1($token); - $newUser = $this->getNewUserData(); + $newUser = $this->getNewUserData(); $newUser['usr_id'] = 1; - $domainUser = new User( + $domainUser = new User( $newUser['usr_id'], $newUser['usr_status_flag'], $newUser['usr_email'], @@ -70,7 +70,7 @@ public function testConstants(): void ); $expected = 'tk-' . $domainUser->id . '-' . $shaToken; - $actual = Cache::getCacheTokenKey($domainUser, $token); + $actual = Cache::getCacheTokenKey($domainUser, $token); $this->assertSame($expected, $actual); } } diff --git a/tests/Unit/Domain/Infrastructure/Constants/DatesTest.php b/tests/Unit/Domain/Infrastructure/Constants/DatesTest.php index 2909f28..b4c88e1 100644 --- a/tests/Unit/Domain/Infrastructure/Constants/DatesTest.php +++ b/tests/Unit/Domain/Infrastructure/Constants/DatesTest.php @@ -41,7 +41,7 @@ public function testConstants(): void $actual = Dates::DATE_TIME_ZONE; $this->assertSame($expected, $actual); - $now = new DateTimeImmutable(); + $now = new DateTimeImmutable(); $expected = $now->format('Y-m-d'); $actual = Dates::toUTC(format: 'Y-m-d'); diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php index 14fa5bc..26ed3da 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php @@ -14,9 +14,8 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\Auth\DTO; use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizer\AuthSanitizer; use Phalcon\Api\Tests\AbstractUnitTestCase; final class AuthInputTest extends AbstractUnitTestCase @@ -24,7 +23,7 @@ final class AuthInputTest extends AbstractUnitTestCase public function testObject(): void { /** @var AuthSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::AUTH_SANITIZER); + $sanitizer = $this->container->get(AuthSanitizer::class); $faker = FakerFactory::create(); // Build an input with many fields present diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizerTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizer/AuthSanitizerTest.php similarity index 89% rename from tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizerTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizer/AuthSanitizerTest.php index 3d0d693..9859430 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizerTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizer/AuthSanitizerTest.php @@ -11,11 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\Auth\Sanitizers; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\Auth\Sanitizer; use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizer\AuthSanitizer; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Filter\Filter; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php index d210cb9..98f8d33 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php @@ -14,9 +14,8 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\DTO; use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizer\UserSanitizer; use Phalcon\Api\Tests\AbstractUnitTestCase; use function json_encode; @@ -26,10 +25,9 @@ final class UserInputTest extends AbstractUnitTestCase public function testToArray(): void { /** @var UserSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::USER_SANITIZER); + $sanitizer = $this->container->get(UserSanitizer::class); $faker = FakerFactory::create(); - // Build an input with many fields present $input = [ 'id' => $faker->numberBetween(1, 1000), 'status' => $faker->numberBetween(0, 9), @@ -44,9 +42,9 @@ public function testToArray(): void 'tokenPassword' => $faker->password(), 'tokenId' => $faker->uuid(), 'preferences' => json_encode(['k' => $faker->word()]), - 'createdDate' => $faker->date('Y-m-d'), + 'createdDate' => $faker->date(), 'createdUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast - 'updatedDate' => $faker->date('Y-m-d'), + 'updatedDate' => $faker->date(), 'updatedUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast ]; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Mapper/UserMapperTest.php similarity index 82% rename from tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Mapper/UserMapperTest.php index 4a03d93..6e0040a 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Mapper/UserMapperTest.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Mappers; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Mapper; use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; use Phalcon\Api\Domain\Infrastructure\Constants\Dates; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; use Phalcon\Api\Tests\AbstractUnitTestCase; use function json_encode; @@ -25,7 +25,9 @@ final class UserMapperTest extends AbstractUnitTestCase { public function testDb(): void { - $faker = FakerFactory::create(); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + $faker = FakerFactory::create(); $preferences = [ 'theme' => $faker->randomElement(['dark', 'light']), @@ -36,27 +38,30 @@ public function testDb(): void $createdDate = $faker->date(Dates::DATE_TIME_FORMAT); $updatedDate = $faker->date(Dates::DATE_TIME_FORMAT); - $user = new UserInput( - $faker->numberBetween(1, 1000), - $faker->numberBetween(0, 9), - $faker->safeEmail(), - $faker->password(), - $faker->optional()->title(), - $faker->firstName(), - $faker->optional()->word(), - $faker->lastName(), - $faker->optional()->suffix(), - $faker->company(), - $faker->optional()->password(), - $faker->uuid(), - $preferencesJson, - $createdDate, - $faker->numberBetween(1, 1000), - $updatedDate, - $faker->numberBetween(1, 1000) - ); - - $mapper = new UserMapper(); + $input = [ + 'id' => $faker->numberBetween(1, 1000), + 'status' => $faker->numberBetween(0, 9), + 'email' => $faker->safeEmail(), + 'password' => $faker->password(), + 'namePrefix' => $faker->optional()->title(), + 'nameFirst' => $faker->firstName(), + 'nameMiddle' => $faker->optional()->word(), + 'nameLast' => $faker->lastName(), + 'nameSuffix' => $faker->optional()->suffix(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->optional()->password(), + 'tokenId' => $faker->uuid(), + 'preferences' => $preferencesJson, + 'createdDate' => $createdDate, + 'createdUserId' => $faker->numberBetween(1, 1000), + 'updatedDate' => $updatedDate, + 'updatedUserId' => $faker->numberBetween(1, 1000), + ]; + + $user = $factory->update($input); + + /** @var UserMapper $mapper */ + $mapper = $this->container->get(UserMapper::class); $row = $mapper->db($user); $expected = [ @@ -86,7 +91,7 @@ public function testDb(): void public function testDomain(): void { $faker = FakerFactory::create(); - $mapper = new UserMapper(); + $mapper = $this->container->get(UserMapper::class); // Empty row: defaults should be applied $emptyUser = $mapper->domain([]); @@ -162,7 +167,7 @@ public function testDomain(): void public function testInput(): void { $faker = FakerFactory::create(); - $mapper = new UserMapper(); + $mapper = $this->container->get(UserMapper::class); $preferences = ['k' => $faker->word()]; $preferencesJson = json_encode($preferences); @@ -181,9 +186,9 @@ public function testInput(): void 'tokenPassword' => $faker->optional()->password(), 'tokenId' => $faker->uuid(), 'preferences' => $preferencesJson, - 'createdDate' => $faker->date('Y-m-d'), + 'createdDate' => $faker->date(), 'createdUserId' => $faker->numberBetween(1, 1000), - 'updatedDate' => $faker->date('Y-m-d'), + 'updatedDate' => $faker->date(), 'updatedUserId' => $faker->numberBetween(1, 1000), ]; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Repository/UserRepositoryTest.php similarity index 92% rename from tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Repository/UserRepositoryTest.php index 07d0070..aaf8a25 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Repository/UserRepositoryTest.php @@ -11,11 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Repositories; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Repository; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; @@ -24,7 +23,7 @@ final class UserRepositoryTest extends AbstractUnitTestCase public function testFindByEmail(): void { /** @var UserRepository $repository */ - $repository = $this->container->get(Container::USER_REPOSITORY); + $repository = $this->container->get(UserRepository::class); $migration = new UsersMigration($this->getConnection()); @@ -42,7 +41,7 @@ public function testFindByEmail(): void public function testFindById(): void { /** @var UserRepository $repository */ - $repository = $this->container->get(Container::USER_REPOSITORY); + $repository = $this->container->get(UserRepository::class); $migration = new UsersMigration($this->getConnection()); diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizerTest.php similarity index 97% rename from tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizerTest.php index 60964a4..9e63b12 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizerTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Sanitizers; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Sanitizer; use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizer\UserSanitizer; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Filter\Filter; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Validator/UserValidatorUpdateTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Validator/UserValidatorUpdateTest.php new file mode 100644 index 0000000..4cbe259 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Validator/UserValidatorUpdateTest.php @@ -0,0 +1,88 @@ + + * + * 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\Infrastructure\DataSource\User\Validator; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizer\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validator\UserValidatorUpdate; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class UserValidatorUpdateTest extends AbstractUnitTestCase +{ + public function testError(): void + { + /** @var UserSanitizer $sanitizer */ + $sanitizer = $this->container->get(UserSanitizer::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + /** @var UserValidatorUpdate $validator */ + $validator = $this->container->get(UserValidatorUpdate::class); + + $input = []; + $userInput = $factory->update($input); + + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = [ + ['Field id is not a valid absolute integer and greater than 0'], + ]; + + $this->assertSame($expected, $actual); + + $input = [ + 'id' => 1, + 'email' => 'not-email', + ]; + $userInput = $factory->update($input); + + /** @var UserValidatorUpdate $validator */ + $validator = $this->container->get(UserValidatorUpdate::class); + + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = [ + ['Field email must be an email address'], + ]; + $this->assertSame($expected, $actual); + } + + public function testSuccess(): void + { + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + /** @var UserValidatorUpdate $validator */ + $validator = $this->container->get(UserValidatorUpdate::class); + $faker = FakerFactory::create(); + + $input = [ + 'id' => $faker->numberBetween(1, 100), + 'email' => $faker->safeEmail(), + 'password' => $faker->password(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + ]; + + $userInput = $factory->update($input); + + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = []; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php deleted file mode 100644 index afab4ff..0000000 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php +++ /dev/null @@ -1,80 +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\Domain\Infrastructure\DataSource\User\Validators; - -use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidator; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidatorUpdate; -use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Filter\Validation\ValidationInterface; - -final class UserValidatorUpdateTest extends AbstractUnitTestCase -{ - public function testError(): void - { - /** @var UserSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::USER_SANITIZER); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); - - $input = []; - $userInput = UserInput::new($sanitizer, $input); - - $validator = new UserValidatorUpdate($validation); - $result = $validator->validate($userInput); - $actual = $result->getErrors(); - - $expected = [ - ['Field id is not a valid absolute integer and greater than 0'], - ['Field email is required'], - ['Field email must be an email address'], - ['Field password is required'], - ['Field issuer is required'], - ['Field tokenPassword is required'], - ['Field tokenId is required'], - ]; - - $this->assertSame($expected, $actual); - } - - public function testSuccess(): void - { - /** @var UserSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::USER_SANITIZER); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); - $faker = FakerFactory::create(); - - $input = [ - 'id' => $faker->numberBetween(1, 100), - 'email' => $faker->safeEmail(), - 'password' => $faker->password(), - 'issuer' => $faker->company(), - 'tokenPassword' => $faker->password(), - 'tokenId' => $faker->uuid(), - ]; - - $userInput = UserInput::new($sanitizer, $input); - - $validator = new UserValidatorUpdate($validation); - $result = $validator->validate($userInput); - $actual = $result->getErrors(); - - $expected = []; - $this->assertSame($expected, $actual); - } -} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php index 63a60bb..4bf619f 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php @@ -18,18 +18,6 @@ final class ResultTest extends AbstractUnitTestCase { - public function testSuccessIsValidAndHasNoErrors(): void - { - $result = Result::success(); - - $actual = $result->isValid(); - $this->assertTrue($actual); - - $expected = []; - $actual = $result->getErrors(); - $this->assertSame($expected, $actual); - } - public function testErrorIsInvalidAndReturnsErrors(): void { $errors = ['field' => ['must not be empty']]; @@ -48,7 +36,7 @@ public function testGetMetaReturnsDefaultWhenMissing(): void $actual = $result->getMeta('present'); $this->assertSame($expected, $actual); - $actual = $result->getMeta('uknown'); + $actual = $result->getMeta('uknown'); $this->assertNull($actual); $expected = 'default'; @@ -60,7 +48,7 @@ public function testSetMetaAddsOrUpdatesMeta(): void { $result = new Result(); - $actual = $result->getMeta('key'); + $actual = $result->getMeta('key'); $this->assertNull($actual); $result->setMeta('key', 'value'); @@ -75,4 +63,16 @@ public function testSetMetaAddsOrUpdatesMeta(): void $actual = $result->getMeta('key'); $this->assertSame($expected, $actual); } + + public function testSuccessIsValidAndHasNoErrors(): void + { + $result = Result::success(); + + $actual = $result->isValid(); + $this->assertTrue($actual); + + $expected = []; + $actual = $result->getErrors(); + $this->assertSame($expected, $actual); + } } diff --git a/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php b/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php index d505046..5913ff9 100644 --- a/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php +++ b/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php @@ -13,7 +13,6 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Encryption; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Encryption\Security; use Phalcon\Api\Tests\AbstractUnitTestCase; @@ -22,7 +21,7 @@ final class SecurityTest extends AbstractUnitTestCase public function testSecurity(): void { /** @var Security $security */ - $security = $this->container->get(Container::SECURITY); + $security = $this->container->get(Security::class); $password = $this->getStrongPassword(); $hashed = $security->hash($password); diff --git a/tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php b/tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php index 411ab54..ac87357 100644 --- a/tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php +++ b/tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php @@ -14,7 +14,6 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Encryption; use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCache; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; @@ -29,11 +28,11 @@ final class TokenCacheTest extends AbstractUnitTestCase public function testStoreAndInvalidateForUser(): void { /** @var EnvManager $env */ - $env = $this->container->get(Container::ENV); + $env = $this->container->get(EnvManager::class); /** @var Cache $cache */ - $cache = $this->container->get(Container::CACHE); + $cache = $this->container->get(Cache::class); /** @var TokenCache $tokenCache */ - $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + $tokenCache = $this->container->get(TokenCache::class); /** * Empty cache diff --git a/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php b/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php index df14ca9..84d1788 100644 --- a/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php +++ b/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php @@ -13,8 +13,21 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Enums\Http; -use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLoginPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLogoutPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthRefreshPostService; +use Phalcon\Api\Domain\Application\User\Service\UserDeleteService; +use Phalcon\Api\Domain\Application\User\Service\UserGetService; +use Phalcon\Api\Domain\Application\User\Service\UserPostService; +use Phalcon\Api\Domain\Application\User\Service\UserPutService; use Phalcon\Api\Domain\Infrastructure\Enums\Http\RoutesEnum; +use Phalcon\Api\Domain\Infrastructure\Middleware\HealthMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\NotFoundMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenClaimsMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenPresenceMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenRevokedMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenStructureMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenUserMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\DataProvider; @@ -29,7 +42,7 @@ public static function getExamples(): array '/login', '/auth/login', RoutesEnum::POST, - Container::AUTH_LOGIN_POST_SERVICE, + AuthLoginPostService::class, ], [ RoutesEnum::authLogoutPost, @@ -37,7 +50,7 @@ public static function getExamples(): array '/logout', '/auth/logout', RoutesEnum::POST, - Container::AUTH_LOGOUT_POST_SERVICE, + AuthLogoutPostService::class, ], [ RoutesEnum::authRefreshPost, @@ -45,7 +58,7 @@ public static function getExamples(): array '/refresh', '/auth/refresh', RoutesEnum::POST, - Container::AUTH_REFRESH_POST_SERVICE, + AuthRefreshPostService::class, ], [ RoutesEnum::userDelete, @@ -53,7 +66,7 @@ public static function getExamples(): array '', '/user', RoutesEnum::DELETE, - Container::USER_DELETE_SERVICE, + UserDeleteService::class, ], [ RoutesEnum::userGet, @@ -61,7 +74,7 @@ public static function getExamples(): array '', '/user', RoutesEnum::GET, - Container::USER_GET_SERVICE, + UserGetService::class, ], [ RoutesEnum::userPost, @@ -69,7 +82,7 @@ public static function getExamples(): array '', '/user', RoutesEnum::POST, - Container::USER_POST_SERVICE, + UserPostService::class, ], [ RoutesEnum::userPut, @@ -77,7 +90,7 @@ public static function getExamples(): array '', '/user', RoutesEnum::PUT, - Container::USER_PUT_SERVICE, + UserPutService::class, ], ]; } @@ -122,13 +135,13 @@ public function testCheckItems( public function testMiddleware(): void { $expected = [ - Container::MIDDLEWARE_NOT_FOUND => RoutesEnum::EVENT_BEFORE, - Container::MIDDLEWARE_HEALTH => RoutesEnum::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_PRESENCE => RoutesEnum::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_STRUCTURE => RoutesEnum::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_USER => RoutesEnum::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_CLAIMS => RoutesEnum::EVENT_BEFORE, - Container::MIDDLEWARE_VALIDATE_TOKEN_REVOKED => RoutesEnum::EVENT_BEFORE, + NotFoundMiddleware::class => RoutesEnum::EVENT_BEFORE, + HealthMiddleware::class => RoutesEnum::EVENT_BEFORE, + ValidateTokenPresenceMiddleware::class => RoutesEnum::EVENT_BEFORE, + ValidateTokenStructureMiddleware::class => RoutesEnum::EVENT_BEFORE, + ValidateTokenUserMiddleware::class => RoutesEnum::EVENT_BEFORE, + ValidateTokenClaimsMiddleware::class => RoutesEnum::EVENT_BEFORE, + ValidateTokenRevokedMiddleware::class => RoutesEnum::EVENT_BEFORE, ]; $actual = RoutesEnum::middleware(); $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php b/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php index e4703a7..7377527 100644 --- a/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php +++ b/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php @@ -13,7 +13,6 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Env\Adapters; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Env\Adapters\DotEnv; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Domain\Infrastructure\Exceptions\InvalidConfigurationArgumentException; @@ -28,10 +27,10 @@ public function setUp(): void parent::setUp(); /** @var EnvManager $env */ - $env = $this->container->get(Container::ENV); + $env = $this->container->get(EnvManager::class); $this->envFile = $env->appPath() - . '/tests/Fixtures/Domain/Components/Env/'; + . '/tests/Fixtures/Domain/Infrastructure/Env/'; } public function testLoadExceptionForEmptyFilePath(): void diff --git a/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php b/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php index 3ff8f53..f5484f5 100644 --- a/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php +++ b/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php @@ -13,7 +13,6 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Env; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -24,7 +23,7 @@ final class EnvManagerTest extends AbstractUnitTestCase public function testAppEnvReturnsDefault(): void { /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $expected = 'development'; $actual = $env->appEnv(); @@ -36,7 +35,7 @@ public function testAppEnvReturnsValue(): void $_ENV = ['APP_ENV' => 'production']; /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $expected = 'production'; $actual = $env->appEnv(); @@ -46,7 +45,7 @@ public function testAppEnvReturnsValue(): void public function testAppLogLevelReturnsDefault(): void { /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $expected = 1; $actual = $env->appLogLevel(); @@ -58,7 +57,7 @@ public function testAppLogLevelReturnsValue(): void $_ENV = ['APP_LOG_LEVEL' => 5]; /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $expected = 5; $actual = $env->appLogLevel(); @@ -68,7 +67,7 @@ public function testAppLogLevelReturnsValue(): void public function testAppPathReturnsRoot(): void { /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $expected = dirname(__DIR__, 5); $actual = $env->appPath(); @@ -78,7 +77,7 @@ public function testAppPathReturnsRoot(): void public function testAppTimezoneReturnsDefault(): void { /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $expected = 'UTC'; $actual = $env->appTimezone(); @@ -90,7 +89,7 @@ public function testAppTimezoneReturnsValue(): void $_ENV = ['APP_TIMEZONE' => 'America/Los_Angeles']; /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $expected = 'America/Los_Angeles'; $actual = $env->appTimezone(); @@ -100,12 +99,12 @@ public function testAppTimezoneReturnsValue(): void public function testGetFromDotEnvLoad(): void { /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $_ENV = [ 'APP_ENV_ADAPTER' => 'dotenv', 'APP_ENV_FILE_PATH' => $env->appPath() - . '/tests/Fixtures/Domain/Components/Env/', + . '/tests/Fixtures/Domain/Infrastructure/Env/', ]; $env->load(); diff --git a/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php index e8c8f2b..36d1dad 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php @@ -27,7 +27,7 @@ final class HealthMiddlewareTest extends AbstractUnitTestCase public function testCall(): void { $application = new Micro($this->container); - $middleware = new HealthMiddleware(); + $middleware = $this->container->get(HealthMiddleware::class); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ diff --git a/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php index e258055..8d0ce4c 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php @@ -29,7 +29,7 @@ final class NotFoundMiddlewareTest extends AbstractUnitTestCase public function testBeforeNotFound(): void { $application = new Micro($this->container); - $middleware = new NotFoundMiddleware(); + $middleware = $this->container->get(NotFoundMiddleware::class); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ @@ -57,7 +57,7 @@ public function testBeforeNotFound(): void public function testCall(): void { $application = new Micro($this->container); - $middleware = new NotFoundMiddleware(); + $middleware = $this->container->get(NotFoundMiddleware::class); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ diff --git a/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php index 3bbbc81..851a4c2 100644 --- a/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php +++ b/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php @@ -13,7 +13,6 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Providers; -use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Cache\Cache; @@ -22,7 +21,7 @@ final class CacheDataProviderTest extends AbstractUnitTestCase public function testCheckRegistration(): void { $expected = Cache::class; - $actual = $this->container->get(Container::CACHE); + $actual = $this->container->get(Cache::class); $this->assertInstanceOf($expected, $actual); } } diff --git a/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php index 6992abe..4937535 100644 --- a/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php +++ b/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php @@ -44,7 +44,7 @@ function () use ($now) { ); /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $provider = new ErrorHandlerProvider(); $provider->register($this->container); @@ -76,7 +76,7 @@ function () use ($now) { ); /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); $provider = new ErrorHandlerProvider(); $provider->register($this->container); @@ -111,9 +111,9 @@ function () use ($now) { $_ENV['APP_LOG_LEVEL'] = 2; /** @var Logger $logger */ - $logger = $this->container->getShared(Container::LOGGER); + $logger = $this->container->getShared(Logger::class); /** @var EnvManager $env */ - $env = $this->container->getShared(Container::ENV); + $env = $this->container->getShared(EnvManager::class); /** @var string $logName */ $logName = $env->get('LOG_FILENAME', 'rest-api'); diff --git a/tests/Unit/Responder/JsonResponderTest.php b/tests/Unit/Responder/JsonResponderTest.php index 91d82f9..d3b84fe 100644 --- a/tests/Unit/Responder/JsonResponderTest.php +++ b/tests/Unit/Responder/JsonResponderTest.php @@ -13,11 +13,12 @@ namespace Phalcon\Api\Tests\Unit\Responder; +use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Responder\JsonResponder; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Http\ResponseInterface; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -34,7 +35,7 @@ public function testFailure(): void /** @var ResponseInterface $response */ $response = $this->container->getShared(Container::RESPONSE); /** @var ResponderInterface $responder */ - $responder = $this->container->get(Container::RESPONDER_JSON); + $responder = $this->container->get(JsonResponder::class); $errorContent = uniqid('error-'); $payload = Payload::unauthorized([[1234 => $errorContent]]); @@ -101,7 +102,7 @@ public function testSuccess(): void /** @var ResponseInterface $response */ $response = $this->container->getShared(Container::RESPONSE); /** @var ResponderInterface $responder */ - $responder = $this->container->getShared(Container::RESPONDER_JSON); + $responder = $this->container->getShared(JsonResponder::class); $dataContent = uniqid('data-'); $payload = Payload::success([$dataContent]); From fac4d8d7c5ad24cd6ef1af6b113a3eef067b5d65 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:04:26 -0600 Subject: [PATCH 05/23] [#.x] - added allowEmpty for validators --- .../AuthLoginValidatorEnum.php | 9 +++++++-- .../AuthTokenValidatorEnum.php | 7 ++++++- .../{Validators => Validator}/UserInsertEnum.php | 13 +++++++++---- .../ValidatorEnumInterface.php | 7 ++++++- 4 files changed, 28 insertions(+), 8 deletions(-) rename src/Domain/Infrastructure/Enums/{Validators => Validator}/AuthLoginValidatorEnum.php (80%) rename src/Domain/Infrastructure/Enums/{Validators => Validator}/AuthTokenValidatorEnum.php (78%) rename src/Domain/Infrastructure/Enums/{Validators => Validator}/UserInsertEnum.php (72%) rename src/Domain/Infrastructure/Enums/{Validators => Validator}/ValidatorEnumInterface.php (75%) diff --git a/src/Domain/Infrastructure/Enums/Validators/AuthLoginValidatorEnum.php b/src/Domain/Infrastructure/Enums/Validator/AuthLoginValidatorEnum.php similarity index 80% rename from src/Domain/Infrastructure/Enums/Validators/AuthLoginValidatorEnum.php rename to src/Domain/Infrastructure/Enums/Validator/AuthLoginValidatorEnum.php index cc536cf..3043b4c 100644 --- a/src/Domain/Infrastructure/Enums/Validators/AuthLoginValidatorEnum.php +++ b/src/Domain/Infrastructure/Enums/Validator/AuthLoginValidatorEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validator; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; @@ -21,12 +21,17 @@ enum AuthLoginValidatorEnum implements ValidatorEnumInterface case email; case password; + public function allowEmpty(): bool + { + return false; + } + public function validators(): array { return match ($this) { self::email => [ PresenceOf::class, - Email::class + Email::class, ], self::password => [PresenceOf::class], }; diff --git a/src/Domain/Infrastructure/Enums/Validators/AuthTokenValidatorEnum.php b/src/Domain/Infrastructure/Enums/Validator/AuthTokenValidatorEnum.php similarity index 78% rename from src/Domain/Infrastructure/Enums/Validators/AuthTokenValidatorEnum.php rename to src/Domain/Infrastructure/Enums/Validator/AuthTokenValidatorEnum.php index 70b7df4..0ba003b 100644 --- a/src/Domain/Infrastructure/Enums/Validators/AuthTokenValidatorEnum.php +++ b/src/Domain/Infrastructure/Enums/Validator/AuthTokenValidatorEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validator; use Phalcon\Filter\Validation\Validator\PresenceOf; @@ -19,6 +19,11 @@ enum AuthTokenValidatorEnum implements ValidatorEnumInterface { case token; + public function allowEmpty(): bool + { + return false; + } + public function validators(): array { return [PresenceOf::class]; diff --git a/src/Domain/Infrastructure/Enums/Validators/UserInsertEnum.php b/src/Domain/Infrastructure/Enums/Validator/UserInsertEnum.php similarity index 72% rename from src/Domain/Infrastructure/Enums/Validators/UserInsertEnum.php rename to src/Domain/Infrastructure/Enums/Validator/UserInsertEnum.php index f9a11f7..f91b54c 100644 --- a/src/Domain/Infrastructure/Enums/Validators/UserInsertEnum.php +++ b/src/Domain/Infrastructure/Enums/Validator/UserInsertEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validator; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; @@ -24,14 +24,19 @@ enum UserInsertEnum implements ValidatorEnumInterface case tokenPassword; case tokenId; + public function allowEmpty(): bool + { + return false; + } + public function validators(): array { return match ($this) { - self::email => [ + self::email => [ PresenceOf::class, - Email::class + Email::class, ], - default => [PresenceOf::class], + default => [PresenceOf::class], }; } } diff --git a/src/Domain/Infrastructure/Enums/Validators/ValidatorEnumInterface.php b/src/Domain/Infrastructure/Enums/Validator/ValidatorEnumInterface.php similarity index 75% rename from src/Domain/Infrastructure/Enums/Validators/ValidatorEnumInterface.php rename to src/Domain/Infrastructure/Enums/Validator/ValidatorEnumInterface.php index 3821a33..50ccb95 100644 --- a/src/Domain/Infrastructure/Enums/Validators/ValidatorEnumInterface.php +++ b/src/Domain/Infrastructure/Enums/Validator/ValidatorEnumInterface.php @@ -11,12 +11,17 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validator; use UnitEnum; interface ValidatorEnumInterface extends UnitEnum { + /** + * @return bool + */ + public function allowEmpty(): bool; + /** * @return array */ From dd2affd01ac0abfeef14f5dedd33529c7833e7d8 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:04:58 -0600 Subject: [PATCH 06/23] [#.x] - added getErrorMessage to the manager --- .../Encryption/TokenManager.php | 21 +++++++++++++++++-- .../Encryption/TokenManagerInterface.php | 9 +++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Domain/Infrastructure/Encryption/TokenManager.php b/src/Domain/Infrastructure/Encryption/TokenManager.php index af67d06..abe5d33 100644 --- a/src/Domain/Infrastructure/Encryption/TokenManager.php +++ b/src/Domain/Infrastructure/Encryption/TokenManager.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Encryption\Security\JWT\Token\Token; use Throwable; @@ -29,6 +29,13 @@ */ final class TokenManager implements TokenManagerInterface { + private string $errorMessage = ''; + + /** + * @param TokenCacheInterface $tokenCache + * @param EnvManager $env + * @param JWTToken $jwtToken + */ public function __construct( private readonly TokenCacheInterface $tokenCache, private readonly EnvManager $env, @@ -36,6 +43,14 @@ public function __construct( ) { } + /** + * @return string + */ + public function getErrorMessage(): string + { + return $this->errorMessage; + } + /** * Parse a token and return either null or a Token object * @@ -50,8 +65,10 @@ public function getObject(?string $token): ?Token } try { + $this->errorMessage = ''; return $this->jwtToken->getObject($token); - } catch (Throwable) { + } catch (Throwable $ex) { + $this->errorMessage = $ex->getMessage(); return null; } } diff --git a/src/Domain/Infrastructure/Encryption/TokenManagerInterface.php b/src/Domain/Infrastructure/Encryption/TokenManagerInterface.php index 48386fc..ea3d78e 100644 --- a/src/Domain/Infrastructure/Encryption/TokenManagerInterface.php +++ b/src/Domain/Infrastructure/Encryption/TokenManagerInterface.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; use Phalcon\Encryption\Security\JWT\Token\Token; /** @@ -28,6 +28,13 @@ */ interface TokenManagerInterface { + /** + * Returns the last error message + * + * @return string + */ + public function getErrorMessage(): string; + /** * Parse a token and return either null or a Token object * From a4881e60f83f81aefa2412bdd56f83eef8da736c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:07:20 -0600 Subject: [PATCH 07/23] [#.x] - refactoring folders --- .../User => Application/User/Service}/UserDeleteService.php | 2 +- .../User => Application/User/Service}/UserGetService.php | 2 +- .../Enums/{Sanitizers => Sanitizer}/SanitizersEnumInterface.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/Domain/{Services/User => Application/User/Service}/UserDeleteService.php (92%) rename src/Domain/{Services/User => Application/User/Service}/UserGetService.php (92%) rename src/Domain/Infrastructure/Enums/{Sanitizers => Sanitizer}/SanitizersEnumInterface.php (86%) diff --git a/src/Domain/Services/User/UserDeleteService.php b/src/Domain/Application/User/Service/UserDeleteService.php similarity index 92% rename from src/Domain/Services/User/UserDeleteService.php rename to src/Domain/Application/User/Service/UserDeleteService.php index 4ed07d0..9148f9e 100644 --- a/src/Domain/Services/User/UserDeleteService.php +++ b/src/Domain/Application/User/Service/UserDeleteService.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\User; +namespace Phalcon\Api\Domain\Application\User\Service; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; diff --git a/src/Domain/Services/User/UserGetService.php b/src/Domain/Application/User/Service/UserGetService.php similarity index 92% rename from src/Domain/Services/User/UserGetService.php rename to src/Domain/Application/User/Service/UserGetService.php index 433ea29..08395f2 100644 --- a/src/Domain/Services/User/UserGetService.php +++ b/src/Domain/Application/User/Service/UserGetService.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\User; +namespace Phalcon\Api\Domain\Application\User\Service; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; diff --git a/src/Domain/Infrastructure/Enums/Sanitizers/SanitizersEnumInterface.php b/src/Domain/Infrastructure/Enums/Sanitizer/SanitizersEnumInterface.php similarity index 86% rename from src/Domain/Infrastructure/Enums/Sanitizers/SanitizersEnumInterface.php rename to src/Domain/Infrastructure/Enums/Sanitizer/SanitizersEnumInterface.php index 0738cfd..bdc84f8 100644 --- a/src/Domain/Infrastructure/Enums/Sanitizers/SanitizersEnumInterface.php +++ b/src/Domain/Infrastructure/Enums/Sanitizer/SanitizersEnumInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Sanitizer; use UnitEnum; From f50e2f2d11b89b930f7db9954a96f43782f62e7d Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:08:32 -0600 Subject: [PATCH 08/23] [#.x] - new command bus and handler locator and tests --- .../Infrastructure/CommandBus/CommandBus.php | 39 +++++++++++++ .../CommandBus/CommandBusInterface.php | 21 +++++++ .../CommandBus/CommandInterface.php | 18 ++++++ .../CommandBus/ContainerHandlerLocator.php | 55 +++++++++++++++++++ .../CommandBus/HandlerInterface.php | 21 +++++++ .../CommandBus/HandlerLocatorInterface.php | 24 ++++++++ .../Exceptions/HandlerRuntimeException.php | 21 +++++++ .../CommandBus/CommandBusTest.php | 51 +++++++++++++++++ .../ContainerHandlerLocatorTest.php | 43 +++++++++++++++ 9 files changed, 293 insertions(+) create mode 100644 src/Domain/Infrastructure/CommandBus/CommandBus.php create mode 100644 src/Domain/Infrastructure/CommandBus/CommandBusInterface.php create mode 100644 src/Domain/Infrastructure/CommandBus/CommandInterface.php create mode 100644 src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php create mode 100644 src/Domain/Infrastructure/CommandBus/HandlerInterface.php create mode 100644 src/Domain/Infrastructure/CommandBus/HandlerLocatorInterface.php create mode 100644 src/Domain/Infrastructure/Exceptions/HandlerRuntimeException.php create mode 100644 tests/Unit/Domain/Infrastructure/CommandBus/CommandBusTest.php create mode 100644 tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.php diff --git a/src/Domain/Infrastructure/CommandBus/CommandBus.php b/src/Domain/Infrastructure/CommandBus/CommandBus.php new file mode 100644 index 0000000..895e790 --- /dev/null +++ b/src/Domain/Infrastructure/CommandBus/CommandBus.php @@ -0,0 +1,39 @@ + + * + * 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\Infrastructure\CommandBus; + +use Phalcon\Api\Domain\ADR\Payload; + +final readonly class CommandBus implements CommandBusInterface +{ + /** + * @param HandlerLocatorInterface $locator + */ + public function __construct( + private HandlerLocatorInterface $locator, + ) { + } + + /** + * @param CommandInterface $command + * + * @return Payload + */ + public function dispatch(CommandInterface $command): Payload + { + $handler = $this->locator->resolve($command); + + return $handler($command); + } +} diff --git a/src/Domain/Infrastructure/CommandBus/CommandBusInterface.php b/src/Domain/Infrastructure/CommandBus/CommandBusInterface.php new file mode 100644 index 0000000..1e43b37 --- /dev/null +++ b/src/Domain/Infrastructure/CommandBus/CommandBusInterface.php @@ -0,0 +1,21 @@ + + * + * 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\Infrastructure\CommandBus; + +use Phalcon\Api\Domain\ADR\Payload; + +interface CommandBusInterface +{ + public function dispatch(CommandInterface $command): Payload; +} diff --git a/src/Domain/Infrastructure/CommandBus/CommandInterface.php b/src/Domain/Infrastructure/CommandBus/CommandInterface.php new file mode 100644 index 0000000..c0c4fad --- /dev/null +++ b/src/Domain/Infrastructure/CommandBus/CommandInterface.php @@ -0,0 +1,18 @@ + + * + * 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\Infrastructure\CommandBus; + +interface CommandInterface +{ +} diff --git a/src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php b/src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php new file mode 100644 index 0000000..adf46ee --- /dev/null +++ b/src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php @@ -0,0 +1,55 @@ + + * + * 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\Infrastructure\CommandBus; + +use Phalcon\Api\Domain\Infrastructure\Exceptions\HandlerRuntimeException; +use Phalcon\Di\DiInterface; + +final readonly class ContainerHandlerLocator implements HandlerLocatorInterface +{ + /** + * @param DiInterface $container + */ + public function __construct( + private DiInterface $container, + ) { + } + + /** + * @param CommandInterface $command + * + * @return HandlerInterface + */ + public function resolve(CommandInterface $command): HandlerInterface + { + $commandClass = get_class($command); + + $handlerClass = str_replace( + 'Command', + 'Handler', + $commandClass + ); + + if (true !== $this->container->has($handlerClass)) { + throw HandlerRuntimeException::new( + "No handler configured for $commandClass" + ); + } + + /** @var HandlerInterface $handler */ + $handler = $this->container->get($handlerClass); + + return $handler; + } +} diff --git a/src/Domain/Infrastructure/CommandBus/HandlerInterface.php b/src/Domain/Infrastructure/CommandBus/HandlerInterface.php new file mode 100644 index 0000000..41e5a98 --- /dev/null +++ b/src/Domain/Infrastructure/CommandBus/HandlerInterface.php @@ -0,0 +1,21 @@ + + * + * 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\Infrastructure\CommandBus; + +use Phalcon\Api\Domain\ADR\Payload; + +interface HandlerInterface +{ + public function __invoke(CommandInterface $command): Payload; +} diff --git a/src/Domain/Infrastructure/CommandBus/HandlerLocatorInterface.php b/src/Domain/Infrastructure/CommandBus/HandlerLocatorInterface.php new file mode 100644 index 0000000..a5bc31e --- /dev/null +++ b/src/Domain/Infrastructure/CommandBus/HandlerLocatorInterface.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\Infrastructure\CommandBus; + +interface HandlerLocatorInterface +{ + /** + * @param CommandInterface $command + * + * @return HandlerInterface + */ + public function resolve(CommandInterface $command): HandlerInterface; +} diff --git a/src/Domain/Infrastructure/Exceptions/HandlerRuntimeException.php b/src/Domain/Infrastructure/Exceptions/HandlerRuntimeException.php new file mode 100644 index 0000000..40bd652 --- /dev/null +++ b/src/Domain/Infrastructure/Exceptions/HandlerRuntimeException.php @@ -0,0 +1,21 @@ + + * + * 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\Infrastructure\Exceptions; + +use InvalidArgumentException; + +class HandlerRuntimeException extends InvalidArgumentException +{ + use ExceptionTrait; +} diff --git a/tests/Unit/Domain/Infrastructure/CommandBus/CommandBusTest.php b/tests/Unit/Domain/Infrastructure/CommandBus/CommandBusTest.php new file mode 100644 index 0000000..bf9ee80 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/CommandBus/CommandBusTest.php @@ -0,0 +1,51 @@ + + * + * 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\Infrastructure\CommandBus; + +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandBus; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Application\Command\CommandFixture; +use Phalcon\Api\Tests\Fixtures\Domain\Application\Handler\HandlerFixture; + +use function uniqid; + +final class CommandBusTest extends AbstractUnitTestCase +{ + public function testDispatch(): void + { + $name = uniqid('name-'); + $command = new CommandFixture($name); + /** + * Register services + */ + $this->container->set(HandlerFixture::class, HandlerFixture::class); + + $bus = $this->container->get(CommandBus::class); + + $result = $bus->dispatch($command); + + $expected = Payload::class; + $actual = $result; + $this->assertInstanceOf($expected, $actual); + + $expected = [ + 'data' => [ + "Name: $name", + ], + ]; + $actual = $result->getResult(); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.php b/tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.php new file mode 100644 index 0000000..89fd346 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.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\Unit\Domain\Infrastructure\CommandBus; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\ContainerHandlerLocator; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Application\Command\CommandFixture; +use Phalcon\Api\Tests\Fixtures\Domain\Application\Handler\HandlerFixture; + +use function uniqid; + +final class ContainerHandlerLocatorTest extends AbstractUnitTestCase +{ + public function testDispatch(): void + { + $name = uniqid('name-'); + $command = new CommandFixture($name); + /** + * Register services + */ + $this->container->set(HandlerFixture::class, HandlerFixture::class); + + /** @var ContainerHandlerLocator $locator */ + $locator = $this->container->get(ContainerHandlerLocator::class); + + $result = $locator->resolve($command); + + $expected = HandlerFixture::class; + $actual = $result; + $this->assertInstanceOf($expected, $actual); + } +} From 07e831360a607dbe14caa94e47284139879d6d56 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:09:02 -0600 Subject: [PATCH 09/23] [#.x] - refactored folders --- .../DataSource/Interface/MapperInterface.php | 49 +++++++++++++++++++ .../Interface/SanitizerInterface.php | 31 ++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/Domain/Infrastructure/DataSource/Interface/MapperInterface.php create mode 100644 src/Domain/Infrastructure/DataSource/Interface/SanitizerInterface.php diff --git a/src/Domain/Infrastructure/DataSource/Interface/MapperInterface.php b/src/Domain/Infrastructure/DataSource/Interface/MapperInterface.php new file mode 100644 index 0000000..edf829a --- /dev/null +++ b/src/Domain/Infrastructure/DataSource/Interface/MapperInterface.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\Domain\Infrastructure\DataSource\Interface; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; + +/** + * Contract for mapping between domain DTO/objects and persistence arrays. + * + * @phpstan-import-type TUser from UserTypes + * @phpstan-import-type TUserDbRecord from UserTypes + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + */ +interface MapperInterface +{ + /** + * Map Domain User -> DB row (usr_*) + * + * @return TUserDomainToDbRecord + */ + public function db(CommandInterface $user): array; + + /** + * Map DB row (usr_*) -> Domain User + * + * @param TUserDbRecord|array{} $row + */ + public function domain(array $row): User; + + /** + * Map input row -> Domain User + * + * @param TUser $row + */ + public function input(array $row): User; +} diff --git a/src/Domain/Infrastructure/DataSource/Interface/SanitizerInterface.php b/src/Domain/Infrastructure/DataSource/Interface/SanitizerInterface.php new file mode 100644 index 0000000..6441fff --- /dev/null +++ b/src/Domain/Infrastructure/DataSource/Interface/SanitizerInterface.php @@ -0,0 +1,31 @@ + + * + * 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\Infrastructure\DataSource\Interface; + +use Phalcon\Api\Domain\ADR\InputTypes; + +/** + * @phpstan-import-type TInputSanitize from InputTypes + */ +interface SanitizerInterface +{ + /** + * Return a sanitized array of the input + * + * @param TInputSanitize $input + * + * @return TInputSanitize + */ + public function sanitize(array $input): array; +} From b9722bcc4e6831e674b9a07061569b906f63b5ae Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:10:21 -0600 Subject: [PATCH 10/23] [#.x] - refactored validators to use commands --- .../AuthLoginValidator.php | 12 ++-- .../AuthTokenValidator.php | 22 +++--- .../UserValidatorTrait.php | 14 ++-- .../Validation/AbstractValidator.php | 20 +++--- .../Validation/ValidatorInterface.php | 6 +- .../Auth/Validator/AuthLoginValidatorTest.php | 64 +++++++++++++++++ .../AuthTokenValidatorTest.php | 62 ++++++++--------- .../Validators/AuthLoginValidatorTest.php | 69 ------------------- .../UserValidatorTest.php | 39 +++++------ 9 files changed, 153 insertions(+), 155 deletions(-) rename src/Domain/Infrastructure/DataSource/Auth/{Validators => Validator}/AuthLoginValidator.php (76%) rename src/Domain/Infrastructure/DataSource/Auth/{Validators => Validator}/AuthTokenValidator.php (78%) rename src/Domain/Infrastructure/DataSource/User/{Validators => Validator}/UserValidatorTrait.php (68%) create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Auth/Validator/AuthLoginValidatorTest.php rename tests/Unit/Domain/Infrastructure/DataSource/Auth/{Validators => Validator}/AuthTokenValidatorTest.php (51%) delete mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php rename tests/Unit/Domain/Infrastructure/DataSource/User/{Validators => Validator}/UserValidatorTest.php (52%) diff --git a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php b/src/Domain/Infrastructure/DataSource/Auth/Validator/AuthLoginValidator.php similarity index 76% rename from src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php rename to src/Domain/Infrastructure/DataSource/Auth/Validator/AuthLoginValidator.php index a987221..275775b 100644 --- a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Validator/AuthLoginValidator.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validator; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Infrastructure\Enums\Validators\AuthLoginValidatorEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validator\AuthLoginValidatorEnum; final class AuthLoginValidator extends AbstractValidator { @@ -27,13 +27,13 @@ final class AuthLoginValidator extends AbstractValidator * Validate a AuthInput and return an array of errors. * Empty array means valid. * - * @param AuthInput $input + * @param CommandInterface $command * * @return Result */ - public function validate(mixed $input): Result + public function validate(CommandInterface $command): Result { - $errors = $this->runValidations($input); + $errors = $this->runValidations($command); if (true !== empty($errors)) { return Result::error( [HttpCodesEnum::AppIncorrectCredentials->error()] diff --git a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php b/src/Domain/Infrastructure/DataSource/Auth/Validator/AuthTokenValidator.php similarity index 78% rename from src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php rename to src/Domain/Infrastructure/DataSource/Auth/Validator/AuthTokenValidator.php index 6b2ed17..a8508f7 100644 --- a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Validator/AuthTokenValidator.php @@ -11,16 +11,18 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validator; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Application\Auth\Command\AuthLogoutPostCommand; +use Phalcon\Api\Domain\Application\Auth\Command\AuthRefreshPostCommand; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Infrastructure\Enums\Validators\AuthTokenValidatorEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validator\AuthTokenValidatorEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Filter\Validation\ValidationInterface; @@ -40,19 +42,23 @@ public function __construct( * Validate a AuthInput and return an array of errors. * Empty array means valid. * - * @param AuthInput $input + * @param CommandInterface $command * * @return Result */ - public function validate(mixed $input): Result + public function validate(CommandInterface $command): Result { - $errors = $this->runValidations($input); + $errors = $this->runValidations($command); if (true !== empty($errors)) { return Result::error([HttpCodesEnum::AppTokenNotPresent->error()]); } + /** + * @todo add the docblock for $command here + */ + /** @var AuthLogoutPostCommand|AuthRefreshPostCommand $command */ /** @var string $token */ - $token = $input->token; + $token = $command->token; $tokenObject = $this->tokenManager->getObject($token); if (null === $tokenObject) { return Result::error([HttpCodesEnum::AppTokenNotValid->error()]); diff --git a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTrait.php b/src/Domain/Infrastructure/DataSource/User/Validator/UserValidatorTrait.php similarity index 68% rename from src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTrait.php rename to src/Domain/Infrastructure/DataSource/User/Validator/UserValidatorTrait.php index 69fd2b9..c2fc0b9 100644 --- a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTrait.php +++ b/src/Domain/Infrastructure/DataSource/User/Validator/UserValidatorTrait.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validator; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; trait UserValidatorTrait @@ -22,13 +22,13 @@ trait UserValidatorTrait * Validate a AuthInput and return an array of errors. * Empty array means valid. * - * @param AuthInput $input + * @param CommandInterface $command * * @return Result */ - public function validate(mixed $input): Result + public function validate(CommandInterface $command): Result { - $errors = $this->runValidations($input); + $errors = $this->runValidations($command); if (true !== empty($errors)) { return Result::error($errors); } @@ -37,9 +37,9 @@ public function validate(mixed $input): Result } /** - * @param AuthInput $input + * @param CommandInterface $command * * @return array */ - abstract protected function runValidations(mixed $input): array; + abstract protected function runValidations(CommandInterface $command): array; } diff --git a/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php b/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php index 2048065..bc15ea5 100644 --- a/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php +++ b/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php @@ -13,8 +13,8 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\Validation; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; -use Phalcon\Api\Domain\Infrastructure\Enums\Validators\ValidatorEnumInterface; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Validator\ValidatorEnumInterface; use Phalcon\Filter\Validation\ValidationInterface; use Phalcon\Filter\Validation\ValidatorInterface as PhalconValidator; @@ -23,18 +23,18 @@ abstract class AbstractValidator implements ValidatorInterface protected string $fields = ''; public function __construct( - private ValidationInterface $validation + private readonly ValidationInterface $validation ) { } /** - * @param AuthInput $input + * @param CommandInterface $command * * @return list> */ - protected function runValidations(mixed $input): array + protected function runValidations(CommandInterface $command): array { - $enum = $this->fields; + $enum = $this->fields; /** @var ValidatorEnumInterface[] $elements */ $elements = $enum::cases(); @@ -43,12 +43,16 @@ protected function runValidations(mixed $input): array $validators = $element->validators(); foreach ($validators as $validator) { /** @var PhalconValidator $validatorObject */ - $validatorObject = new $validator(); + $validatorObject = new $validator( + [ + 'allowEmpty' => $element->allowEmpty(), + ] + ); $this->validation->add($element->name, $validatorObject); } } - $this->validation->validate($input); + $this->validation->validate($command); $messages = $this->validation->getMessages(); $results = []; diff --git a/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php b/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php index e79bacd..3177cd9 100644 --- a/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php +++ b/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php @@ -13,6 +13,8 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\Validation; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + /** * Validator contract. Accepts a DTO or input and returns a Result. */ @@ -21,9 +23,9 @@ interface ValidatorInterface /** * Validate a DTO or input structure. * - * @param mixed $input DTO or array + * @param CommandInterface $command DTO or array * * @return Result */ - public function validate(mixed $input): Result; + public function validate(CommandInterface $command): Result; } diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validator/AuthLoginValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validator/AuthLoginValidatorTest.php new file mode 100644 index 0000000..cfb082e --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validator/AuthLoginValidatorTest.php @@ -0,0 +1,64 @@ + + * + * 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\Infrastructure\DataSource\Auth\Validator; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactory; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validator\AuthLoginValidator; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class AuthLoginValidatorTest extends AbstractUnitTestCase +{ + public function testError(): void + { + /** @var AuthLoginValidator $validator */ + $validator = $this->container->get(AuthLoginValidator::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $input = []; + $authCommand = $factory->authenticate($input); + + $result = $validator->validate($authCommand); + $actual = $result->getErrors(); + + $expected = [ + HttpCodesEnum::AppIncorrectCredentials->error(), + ]; + + $this->assertSame($expected, $actual); + } + + public function testSuccess(): void + { + /** @var AuthLoginValidator $validator */ + $validator = $this->container->get(AuthLoginValidator::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + $faker = FakerFactory::create(); + + $input = [ + 'email' => $faker->safeEmail(), + 'password' => $faker->password(), + ]; + $authCommand = $factory->authenticate($input); + + $result = $validator->validate($authCommand); + $actual = $result->getErrors(); + + $expected = []; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validator/AuthTokenValidatorTest.php similarity index 51% rename from tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidatorTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/Auth/Validator/AuthTokenValidatorTest.php index 333d74d..adbc0d0 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidatorTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validator/AuthTokenValidatorTest.php @@ -11,42 +11,36 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\Auth\Validators; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\Auth\Validator; use Exception; -use Faker\Factory; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthTokenValidator; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactory; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizer\AuthSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validator\AuthTokenValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCache; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCacheInterface; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Filter\Validation\ValidationInterface; +use Phalcon\Filter\Validation; final class AuthTokenValidatorTest extends AbstractUnitTestCase { public function testFailureTokenNotPresent(): void { - /** @var AuthSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::AUTH_SANITIZER); - /** @var TokenManager $tokenManager */ - $tokenManager = $this->container->get(Container::JWT_TOKEN_MANAGER); - /** @var UserRepository $repository */ - $repository = $this->container->get(Container::USER_REPOSITORY); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); + /** @var AuthTokenValidator $validator */ + $validator = $this->container->get(AuthTokenValidator::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); - $input = []; - $userInput = AuthInput::new($sanitizer, $input); + $input = []; + $authCommand = $factory->logout($input); - $validator = new AuthTokenValidator($tokenManager, $repository, $validation); - $result = $validator->validate($userInput); - $actual = $result->getErrors(); + $result = $validator->validate($authCommand); + $actual = $result->getErrors(); $expected = [ HttpCodesEnum::AppTokenNotPresent->error(), @@ -58,22 +52,24 @@ public function testFailureTokenNotPresent(): void public function testFailureTokenNotValid(): void { /** @var EnvManager $env */ - $env = $this->container->get(Container::ENV); + $env = $this->container->get(EnvManager::class); /** @var TokenCacheInterface $tokenCache */ - $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + $tokenCache = $this->container->get(TokenCache::class); /** @var AuthSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::AUTH_SANITIZER); + $sanitizer = $this->container->get(AuthSanitizer::class); /** @var UserRepository $repository */ - $repository = $this->container->get(Container::USER_REPOSITORY); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); + $repository = $this->container->get(UserRepository::class); + /** @var Validation $validator */ + $validation = $this->container->get(Validation::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); $mockJWTToken = $this ->getMockBuilder(JWTToken::class) ->disableOriginalConstructor() ->onlyMethods( [ - 'getObject' + 'getObject', ] ) ->getMock() @@ -83,19 +79,19 @@ public function testFailureTokenNotValid(): void ->willThrowException(new Exception('error')) ; - $userData = $this->getNewUserData(); + $userData = $this->getNewUserData(); $userData['usr_id'] = rand(1, 100); - $token = $this->getUserToken($userData); + $token = $this->getUserToken($userData); $tokenManager = new TokenManager($tokenCache, $env, $mockJWTToken); - $input = [ + $input = [ 'token' => $token, ]; - $userInput = AuthInput::new($sanitizer, $input); + $authCommand = $factory->logout($input); $validator = new AuthTokenValidator($tokenManager, $repository, $validation); - $result = $validator->validate($userInput); + $result = $validator->validate($authCommand); $actual = $result->getErrors(); $expected = [ diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php deleted file mode 100644 index db79869..0000000 --- a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php +++ /dev/null @@ -1,69 +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\Domain\Infrastructure\DataSource\Auth\Validators; - -use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthLoginValidator; -use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Filter\Validation\ValidationInterface; - -final class AuthLoginValidatorTest extends AbstractUnitTestCase -{ - public function testError(): void - { - /** @var AuthSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::AUTH_SANITIZER); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); - - $input = []; - $userInput = AuthInput::new($sanitizer, $input); - - $validator = new AuthLoginValidator($validation); - $result = $validator->validate($userInput); - $actual = $result->getErrors(); - - $expected = [ - HttpCodesEnum::AppIncorrectCredentials->error(), - ]; - - $this->assertSame($expected, $actual); - } - - public function testSuccess(): void - { - /** @var AuthSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::AUTH_SANITIZER); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); - $faker = FakerFactory::create(); - - $input = [ - 'email' => $faker->safeEmail(), - 'password' => $faker->password(), - ]; - $userInput = AuthInput::new($sanitizer, $input); - - $validator = new AuthLoginValidator($validation); - $result = $validator->validate($userInput); - $actual = $result->getErrors(); - - $expected = []; - $this->assertSame($expected, $actual); - } -} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Validator/UserValidatorTest.php similarity index 52% rename from tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Validator/UserValidatorTest.php index 155fdd0..c81cd62 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Validator/UserValidatorTest.php @@ -11,31 +11,27 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Validators; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Validator; use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidator; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validator\UserValidator; use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Filter\Validation\ValidationInterface; final class UserValidatorTest extends AbstractUnitTestCase { public function testError(): void { - /** @var UserSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::USER_SANITIZER); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + /** @var UserValidator $validator */ + $validator = $this->container->get(UserValidator::class); $input = []; - $userInput = UserInput::new($sanitizer, $input); + $userInput = $factory->insert($input); - $validator = new UserValidator($validation); - $result = $validator->validate($userInput); - $actual = $result->getErrors(); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); $expected = [ ['Field email is required'], @@ -51,10 +47,10 @@ public function testError(): void public function testSuccess(): void { - /** @var UserSanitizer $sanitizer */ - $sanitizer = $this->container->get(Container::USER_SANITIZER); - /** @var ValidationInterface $validation */ - $validation = $this->container->get(Container::VALIDATION); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + /** @var UserValidator $validator */ + $validator = $this->container->get(UserValidator::class); $faker = FakerFactory::create(); $input = [ @@ -65,11 +61,10 @@ public function testSuccess(): void 'tokenId' => $faker->uuid(), ]; - $userInput = UserInput::new($sanitizer, $input); + $userInput = $factory->insert($input); - $validator = new UserValidator($validation); - $result = $validator->validate($userInput); - $actual = $result->getErrors(); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); $expected = []; $this->assertSame($expected, $actual); From bc0e7f799909c10c3177779fe34e90b0af8eae23 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:10:57 -0600 Subject: [PATCH 11/23] [#.x] - added auth command objects --- .../Auth/Command/AuthLoginPostCommand.php | 29 +++++++++++ .../Auth/Command/AuthLogoutPostCommand.php | 27 ++++++++++ .../Auth/Command/AuthRefreshPostCommand.php | 27 ++++++++++ .../Auth/Command/AuthLoginPostCommandTest.php | 49 +++++++++++++++++++ .../Command/AuthLogoutPostCommandTest.php | 42 ++++++++++++++++ .../Command/AuthRefreshPostCommandTest.php | 42 ++++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 src/Domain/Application/Auth/Command/AuthLoginPostCommand.php create mode 100644 src/Domain/Application/Auth/Command/AuthLogoutPostCommand.php create mode 100644 src/Domain/Application/Auth/Command/AuthRefreshPostCommand.php create mode 100644 tests/Unit/Domain/Application/Auth/Command/AuthLoginPostCommandTest.php create mode 100644 tests/Unit/Domain/Application/Auth/Command/AuthLogoutPostCommandTest.php create mode 100644 tests/Unit/Domain/Application/Auth/Command/AuthRefreshPostCommandTest.php diff --git a/src/Domain/Application/Auth/Command/AuthLoginPostCommand.php b/src/Domain/Application/Auth/Command/AuthLoginPostCommand.php new file mode 100644 index 0000000..ee02ab3 --- /dev/null +++ b/src/Domain/Application/Auth/Command/AuthLoginPostCommand.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\Application\Auth\Command; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + +final readonly class AuthLoginPostCommand implements CommandInterface +{ + /** + * @param string|null $email + * @param string|null $password + */ + public function __construct( + public ?string $email, + public ?string $password, + ) { + } +} diff --git a/src/Domain/Application/Auth/Command/AuthLogoutPostCommand.php b/src/Domain/Application/Auth/Command/AuthLogoutPostCommand.php new file mode 100644 index 0000000..8eb47e2 --- /dev/null +++ b/src/Domain/Application/Auth/Command/AuthLogoutPostCommand.php @@ -0,0 +1,27 @@ + + * + * 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\Application\Auth\Command; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + +final readonly class AuthLogoutPostCommand implements CommandInterface +{ + /** + * @param string|null $token + */ + public function __construct( + public ?string $token + ) { + } +} diff --git a/src/Domain/Application/Auth/Command/AuthRefreshPostCommand.php b/src/Domain/Application/Auth/Command/AuthRefreshPostCommand.php new file mode 100644 index 0000000..2f16444 --- /dev/null +++ b/src/Domain/Application/Auth/Command/AuthRefreshPostCommand.php @@ -0,0 +1,27 @@ + + * + * 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\Application\Auth\Command; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + +final readonly class AuthRefreshPostCommand implements CommandInterface +{ + /** + * @param string|null $token + */ + public function __construct( + public ?string $token + ) { + } +} diff --git a/tests/Unit/Domain/Application/Auth/Command/AuthLoginPostCommandTest.php b/tests/Unit/Domain/Application/Auth/Command/AuthLoginPostCommandTest.php new file mode 100644 index 0000000..61bdc08 --- /dev/null +++ b/tests/Unit/Domain/Application/Auth/Command/AuthLoginPostCommandTest.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\Unit\Domain\Application\Auth\Command; + +use Phalcon\Api\Domain\Application\Auth\Command\AuthLoginPostCommand; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class AuthLoginPostCommandTest extends AbstractUnitTestCase +{ + public function testConstruct(): void + { + $email = 'user@example.com'; + $password = 's3cr3t'; + + $command = new AuthLoginPostCommand($email, $password); + + $expected = $email; + $actual = $command->email; + $this->assertSame($expected, $actual); + + $expected = $password; + $actual = $command->password; + $this->assertSame($expected, $actual); + } + + public function testConstructEmpty(): void + { + $command = new AuthLoginPostCommand(null, null); + + $expected = null; + $actual = $command->email; + $this->assertSame($expected, $actual); + + $expected = null; + $actual = $command->password; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Application/Auth/Command/AuthLogoutPostCommandTest.php b/tests/Unit/Domain/Application/Auth/Command/AuthLogoutPostCommandTest.php new file mode 100644 index 0000000..c7c82fa --- /dev/null +++ b/tests/Unit/Domain/Application/Auth/Command/AuthLogoutPostCommandTest.php @@ -0,0 +1,42 @@ + + * + * 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\Application\Auth\Command; + +use Phalcon\Api\Domain\Application\Auth\Command\AuthLogoutPostCommand; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +use function uniqid; + +final class AuthLogoutPostCommandTest extends AbstractUnitTestCase +{ + public function testConstruct(): void + { + $token = uniqid('token-'); + + $command = new AuthLogoutPostCommand($token); + + $expected = $token; + $actual = $command->token; + $this->assertSame($expected, $actual); + } + + public function testConstructEmpty(): void + { + $command = new AuthLogoutPostCommand(null); + + $expected = null; + $actual = $command->token; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Application/Auth/Command/AuthRefreshPostCommandTest.php b/tests/Unit/Domain/Application/Auth/Command/AuthRefreshPostCommandTest.php new file mode 100644 index 0000000..9123345 --- /dev/null +++ b/tests/Unit/Domain/Application/Auth/Command/AuthRefreshPostCommandTest.php @@ -0,0 +1,42 @@ + + * + * 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\Application\Auth\Command; + +use Phalcon\Api\Domain\Application\Auth\Command\AuthRefreshPostCommand; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +use function uniqid; + +final class AuthRefreshPostCommandTest extends AbstractUnitTestCase +{ + public function testConstruct(): void + { + $token = uniqid('token-'); + + $command = new AuthRefreshPostCommand($token); + + $expected = $token; + $actual = $command->token; + $this->assertSame($expected, $actual); + } + + public function testConstructEmpty(): void + { + $command = new AuthRefreshPostCommand(null); + + $expected = null; + $actual = $command->token; + $this->assertSame($expected, $actual); + } +} From 497e0b29afa96b6991f5cd0b8d9aa12c1e729270 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:11:51 -0600 Subject: [PATCH 12/23] [#.x] - added user command objects and factory --- .../User/Command/UserCommandFactory.php | 137 +++++++++ .../Command/UserCommandFactoryInterface.php | 50 ++++ .../User/Command/UserDeleteCommand.php | 27 ++ .../User/Command/UserGetCommand.php | 27 ++ .../User/Command/UserPostCommand.php | 59 ++++ .../User/Command/UserPutCommand.php | 59 ++++ .../User/Command/UserCommandFactoryTest.php | 267 ++++++++++++++++++ 7 files changed, 626 insertions(+) create mode 100644 src/Domain/Application/User/Command/UserCommandFactory.php create mode 100644 src/Domain/Application/User/Command/UserCommandFactoryInterface.php create mode 100644 src/Domain/Application/User/Command/UserDeleteCommand.php create mode 100644 src/Domain/Application/User/Command/UserGetCommand.php create mode 100644 src/Domain/Application/User/Command/UserPostCommand.php create mode 100644 src/Domain/Application/User/Command/UserPutCommand.php create mode 100644 tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php diff --git a/src/Domain/Application/User/Command/UserCommandFactory.php b/src/Domain/Application/User/Command/UserCommandFactory.php new file mode 100644 index 0000000..1dae88e --- /dev/null +++ b/src/Domain/Application/User/Command/UserCommandFactory.php @@ -0,0 +1,137 @@ + + * + * 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\Application\User\Command; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interface\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; + +/** + * @phpstan-import-type TUserInput from InputTypes + */ +final readonly class UserCommandFactory implements UserCommandFactoryInterface +{ + public function __construct( + private SanitizerInterface $sanitizer + ) { + } + + /** + * @param TUserInput $input + * + * @return UserDeleteCommand + */ + public function delete(array $input): UserDeleteCommand + { + /** + * Sanitize the input + */ + $dto = UserInput::new($this->sanitizer, $input); + + /** + * Return the DTO back + */ + return new UserDeleteCommand($dto->id); + } + + /** + * @param TUserInput $input + * + * @return UserGetCommand + */ + public function get(array $input): UserGetCommand + { + /** + * Sanitize the input + */ + $dto = UserInput::new($this->sanitizer, $input); + + /** + * Return the DTO back + */ + return new UserGetCommand($dto->id); + } + + /** + * @param TUserInput $input + * + * @return UserPostCommand + */ + public function insert(array $input): UserPostCommand + { + /** + * Sanitize the input + */ + $dto = UserInput::new($this->sanitizer, $input); + + /** + * Return the DTO back + */ + return new UserPostCommand( + null, + $dto->status, + $dto->email, + $dto->password, + $dto->namePrefix, + $dto->nameFirst, + $dto->nameMiddle, + $dto->nameLast, + $dto->nameSuffix, + $dto->issuer, + $dto->tokenPassword, + $dto->tokenId, + $dto->preferences, + $dto->createdDate, + $dto->createdUserId, + $dto->updatedDate, + $dto->updatedUserId, + ); + } + + /** + * @param TUserInput $input + * + * @return UserPutCommand + */ + public function update(array $input): UserPutCommand + { + /** + * Sanitize the input + */ + $dto = UserInput::new($this->sanitizer, $input); + + /** + * Return the DTO back + */ + return new UserPutCommand( + $dto->id, + $dto->status, + $dto->email, + $dto->password, + $dto->namePrefix, + $dto->nameFirst, + $dto->nameMiddle, + $dto->nameLast, + $dto->nameSuffix, + $dto->issuer, + $dto->tokenPassword, + $dto->tokenId, + $dto->preferences, + $dto->createdDate, + $dto->createdUserId, + $dto->updatedDate, + $dto->updatedUserId, + ); + } +} diff --git a/src/Domain/Application/User/Command/UserCommandFactoryInterface.php b/src/Domain/Application/User/Command/UserCommandFactoryInterface.php new file mode 100644 index 0000000..b8645f4 --- /dev/null +++ b/src/Domain/Application/User/Command/UserCommandFactoryInterface.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\Application\User\Command; + +use Phalcon\Api\Domain\ADR\InputTypes; + +/** + * @phpstan-import-type TUserInput from InputTypes + */ +interface UserCommandFactoryInterface +{ + /** + * @param TUserInput $input + * + * @return UserDeleteCommand + */ + public function delete(array $input): UserDeleteCommand; + + /** + * @param TUserInput $input + * + * @return UserGetCommand + */ + public function get(array $input): UserGetCommand; + + /** + * @param TUserInput $input + * + * @return UserPostCommand + */ + public function insert(array $input): UserPostCommand; + + /** + * @param TUserInput $input + * + * @return UserPutCommand + */ + public function update(array $input): UserPutCommand; +} diff --git a/src/Domain/Application/User/Command/UserDeleteCommand.php b/src/Domain/Application/User/Command/UserDeleteCommand.php new file mode 100644 index 0000000..86db29c --- /dev/null +++ b/src/Domain/Application/User/Command/UserDeleteCommand.php @@ -0,0 +1,27 @@ + + * + * 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\Application\User\Command; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + +final readonly class UserDeleteCommand implements CommandInterface +{ + /** + * @param int $id + */ + public function __construct( + public int $id, + ) { + } +} diff --git a/src/Domain/Application/User/Command/UserGetCommand.php b/src/Domain/Application/User/Command/UserGetCommand.php new file mode 100644 index 0000000..33510b9 --- /dev/null +++ b/src/Domain/Application/User/Command/UserGetCommand.php @@ -0,0 +1,27 @@ + + * + * 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\Application\User\Command; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + +final readonly class UserGetCommand implements CommandInterface +{ + /** + * @param int $id + */ + public function __construct( + public int $id, + ) { + } +} diff --git a/src/Domain/Application/User/Command/UserPostCommand.php b/src/Domain/Application/User/Command/UserPostCommand.php new file mode 100644 index 0000000..c0ac15e --- /dev/null +++ b/src/Domain/Application/User/Command/UserPostCommand.php @@ -0,0 +1,59 @@ + + * + * 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\Application\User\Command; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + +final readonly class UserPostCommand implements CommandInterface +{ + /** + * @param int|null $id + * @param int $status + * @param string|null $email + * @param string|null $password + * @param string|null $namePrefix + * @param string|null $nameFirst + * @param string|null $nameMiddle + * @param string|null $nameLast + * @param string|null $nameSuffix + * @param string|null $issuer + * @param string|null $tokenPassword + * @param string|null $tokenId + * @param string|null $preferences + * @param string|null $createdDate + * @param int|null $createdUserId + * @param string|null $updatedDate + * @param int|null $updatedUserId + */ + public function __construct( + public ?int $id, + public int $status, + public ?string $email, + public ?string $password, + public ?string $namePrefix, + public ?string $nameFirst, + public ?string $nameMiddle, + public ?string $nameLast, + public ?string $nameSuffix, + public ?string $issuer, + public ?string $tokenPassword, + public ?string $tokenId, + public ?string $preferences, + public ?string $createdDate, + public ?int $createdUserId, + public ?string $updatedDate, + public ?int $updatedUserId, + ) { + } +} diff --git a/src/Domain/Application/User/Command/UserPutCommand.php b/src/Domain/Application/User/Command/UserPutCommand.php new file mode 100644 index 0000000..11bc512 --- /dev/null +++ b/src/Domain/Application/User/Command/UserPutCommand.php @@ -0,0 +1,59 @@ + + * + * 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\Application\User\Command; + +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; + +final readonly class UserPutCommand implements CommandInterface +{ + /** + * @param int $id + * @param int $status + * @param string|null $email + * @param string|null $password + * @param string|null $namePrefix + * @param string|null $nameFirst + * @param string|null $nameMiddle + * @param string|null $nameLast + * @param string|null $nameSuffix + * @param string|null $issuer + * @param string|null $tokenPassword + * @param string|null $tokenId + * @param string|null $preferences + * @param string|null $createdDate + * @param int|null $createdUserId + * @param string|null $updatedDate + * @param int|null $updatedUserId + */ + public function __construct( + public int $id, + public int $status, + public ?string $email, + public ?string $password, + public ?string $namePrefix, + public ?string $nameFirst, + public ?string $nameMiddle, + public ?string $nameLast, + public ?string $nameSuffix, + public ?string $issuer, + public ?string $tokenPassword, + public ?string $tokenId, + public ?string $preferences, + public ?string $createdDate, + public ?int $createdUserId, + public ?string $updatedDate, + public ?int $updatedUserId, + ) { + } +} diff --git a/tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php b/tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php new file mode 100644 index 0000000..310519b --- /dev/null +++ b/tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php @@ -0,0 +1,267 @@ + + * + * 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\Application\User\Command; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Application\User\Command\UserDeleteCommand; +use Phalcon\Api\Domain\Application\User\Command\UserGetCommand; +use Phalcon\Api\Domain\Application\User\Command\UserPostCommand; +use Phalcon\Api\Domain\Application\User\Command\UserPutCommand; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizer\UserSanitizer; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Filter; + +use function json_encode; + +final class UserCommandFactoryTest extends AbstractUnitTestCase +{ + public function testDelete(): void + { + $sanitizer = $this->container->get(UserSanitizer::class); + $factory = new UserCommandFactory($sanitizer); + $faker = FakerFactory::create(); + + $input = [ + 'id' => $faker->numberBetween(1, 1000), + 'status' => $faker->numberBetween(0, 9), + 'email' => " Foo.Bar+tag@Example.COM ", + 'password' => $faker->password(), + 'namePrefix' => $faker->title(), + 'nameFirst' => $faker->firstName(), + 'nameMiddle' => $faker->word(), + 'nameLast' => $faker->lastName(), + 'nameSuffix' => $faker->suffix(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + 'preferences' => json_encode(['k' => $faker->word()]), + 'createdDate' => $faker->date(), + 'createdUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + 'updatedDate' => $faker->date(), + 'updatedUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + ]; + + /** @var UserDeleteCommand $command */ + $command = $factory->delete($input); + + $this->assertInstanceOf(UserDeleteCommand::class, $command); + + $expected = $input['id']; + $actual = $command->id; + $this->assertSame($expected, $actual); + } + + public function testGet(): void + { + $sanitizer = $this->container->get(UserSanitizer::class); + $factory = new UserCommandFactory($sanitizer); + $faker = FakerFactory::create(); + $input = [ + 'id' => $faker->numberBetween(1, 1000), + 'status' => $faker->numberBetween(0, 9), + 'email' => " Foo.Bar+tag@Example.COM ", + 'password' => $faker->password(), + 'namePrefix' => $faker->title(), + 'nameFirst' => $faker->firstName(), + 'nameMiddle' => $faker->word(), + 'nameLast' => $faker->lastName(), + 'nameSuffix' => $faker->suffix(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + 'preferences' => json_encode(['k' => $faker->word()]), + 'createdDate' => $faker->date(), + 'createdUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + 'updatedDate' => $faker->date(), + 'updatedUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + ]; + + $command = $factory->get($input); + + $this->assertInstanceOf(UserGetCommand::class, $command); + $expected = $input['id']; + $actual = $command->id; + $this->assertSame($expected, $actual); + } + + public function testInsert(): void + { + $sanitizer = $this->container->get(UserSanitizer::class); + /** @var Filter $filter */ + $filter = $this->container->get(Container::FILTER); + $factory = new UserCommandFactory($sanitizer); + $faker = FakerFactory::create(); + + $input = [ + 'id' => $faker->numberBetween(1, 1000), + 'status' => $faker->numberBetween(0, 9), + 'email' => " Foo.Bar+tag@Example.COM ", + 'password' => $faker->password(), + 'namePrefix' => $faker->title(), + 'nameFirst' => $faker->firstName(), + 'nameMiddle' => $faker->word(), + 'nameLast' => $faker->lastName(), + 'nameSuffix' => $faker->suffix(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + 'preferences' => json_encode(['k' => $faker->word()]), + 'createdDate' => $faker->date(), + 'createdUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + 'updatedDate' => $faker->date(), + 'updatedUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + ]; + + /** @var UserPostCommand $command */ + $command = $factory->insert($input); + + $this->assertInstanceOf(UserPostCommand::class, $command); + + $expected = $input['status']; + $actual = $command->status; + $this->assertSame($expected, $actual); + $expected = $filter->email($input['email']); + $actual = $command->email; + $this->assertSame($expected, $actual); + $expected = $input['password']; + $actual = $command->password; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['namePrefix']); + $actual = $command->namePrefix; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameFirst']); + $actual = $command->nameFirst; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameMiddle']); + $actual = $command->nameMiddle; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameLast']); + $actual = $command->nameLast; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameSuffix']); + $actual = $command->nameSuffix; + $this->assertSame($expected, $actual); + $expected = $input['issuer']; + $actual = $command->issuer; + $this->assertSame($expected, $actual); + $expected = $input['tokenPassword']; + $actual = $command->tokenPassword; + $this->assertSame($expected, $actual); + $expected = $input['tokenId']; + $actual = $command->tokenId; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['preferences']); + $actual = $command->preferences; + $this->assertSame($expected, $actual); + $expected = $input['createdDate']; + $actual = $command->createdDate; + $this->assertSame($expected, $actual); + $expected = $filter->absint($input['createdUserId']); + $actual = $command->createdUserId; + $this->assertSame($expected, $actual); + $expected = $input['updatedDate']; + $actual = $command->updatedDate; + $this->assertSame($expected, $actual); + $expected = $filter->absint($input['updatedUserId']); + $actual = $command->updatedUserId; + $this->assertSame($expected, $actual); + } + + public function testUpdate(): void + { + $sanitizer = $this->container->get(UserSanitizer::class); + /** @var Filter $filter */ + $filter = $this->container->get(Container::FILTER); + $factory = new UserCommandFactory($sanitizer); + $faker = FakerFactory::create(); + + $input = [ + 'id' => $faker->numberBetween(1, 1000), + 'status' => $faker->numberBetween(0, 9), + 'email' => " Foo.Bar+tag@Example.COM ", + 'password' => $faker->password(), + 'namePrefix' => $faker->title(), + 'nameFirst' => $faker->firstName(), + 'nameMiddle' => $faker->word(), + 'nameLast' => $faker->lastName(), + 'nameSuffix' => $faker->suffix(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + 'preferences' => json_encode(['k' => $faker->word()]), + 'createdDate' => $faker->date(), + 'createdUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + 'updatedDate' => $faker->date(), + 'updatedUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + ]; + + /** @var UserPutCommand $command */ + $command = $factory->update($input); + + $this->assertInstanceOf(UserPutCommand::class, $command); + $expected = $input['id']; + $actual = $command->id; + $this->assertSame($expected, $actual); + $expected = $input['status']; + $actual = $command->status; + $this->assertSame($expected, $actual); + $expected = $filter->email($input['email']); + $actual = $command->email; + $this->assertSame($expected, $actual); + $expected = $input['password']; + $actual = $command->password; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['namePrefix']); + $actual = $command->namePrefix; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameFirst']); + $actual = $command->nameFirst; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameMiddle']); + $actual = $command->nameMiddle; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameLast']); + $actual = $command->nameLast; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['nameSuffix']); + $actual = $command->nameSuffix; + $this->assertSame($expected, $actual); + $expected = $input['issuer']; + $actual = $command->issuer; + $this->assertSame($expected, $actual); + $expected = $input['tokenPassword']; + $actual = $command->tokenPassword; + $this->assertSame($expected, $actual); + $expected = $input['tokenId']; + $actual = $command->tokenId; + $this->assertSame($expected, $actual); + $expected = $filter->string($input['preferences']); + $actual = $command->preferences; + $this->assertSame($expected, $actual); + $expected = $input['createdDate']; + $actual = $command->createdDate; + $this->assertSame($expected, $actual); + $expected = $filter->absint($input['createdUserId']); + $actual = $command->createdUserId; + $this->assertSame($expected, $actual); + $expected = $input['updatedDate']; + $actual = $command->updatedDate; + $this->assertSame($expected, $actual); + $expected = $filter->absint($input['updatedUserId']); + $actual = $command->updatedUserId; + $this->assertSame($expected, $actual); + } +} From fc10c444b91d5ab331d03b57d6d5b7212c8d5a4d Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:12:55 -0600 Subject: [PATCH 13/23] [#.x] - refactored services --- .../Auth/Service}/AbstractAuthService.php | 6 ++---- .../Auth/Service/AuthLoginPostService.php} | 6 +++--- .../Auth/Service/AuthLogoutPostService.php} | 6 +++--- .../Auth/Service/AuthRefreshPostService.php} | 6 +++--- 4 files changed, 11 insertions(+), 13 deletions(-) rename src/Domain/{Services/Auth => Application/Auth/Service}/AbstractAuthService.php (64%) rename src/Domain/{Services/Auth/LoginPostService.php => Application/Auth/Service/AuthLoginPostService.php} (76%) rename src/Domain/{Services/Auth/LogoutPostService.php => Application/Auth/Service/AuthLogoutPostService.php} (76%) rename src/Domain/{Services/Auth/RefreshPostService.php => Application/Auth/Service/AuthRefreshPostService.php} (76%) diff --git a/src/Domain/Services/Auth/AbstractAuthService.php b/src/Domain/Application/Auth/Service/AbstractAuthService.php similarity index 64% rename from src/Domain/Services/Auth/AbstractAuthService.php rename to src/Domain/Application/Auth/Service/AbstractAuthService.php index 8e4dd98..5ec195f 100644 --- a/src/Domain/Services/Auth/AbstractAuthService.php +++ b/src/Domain/Application/Auth/Service/AbstractAuthService.php @@ -11,17 +11,15 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\Auth; +namespace Phalcon\Api\Domain\Application\Auth\Service; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Facades\AuthFacade; -use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Application\Auth\Facade\AuthFacade; abstract class AbstractAuthService implements DomainInterface { public function __construct( protected readonly AuthFacade $facade, - protected readonly ValidatorInterface $validator ) { } } diff --git a/src/Domain/Services/Auth/LoginPostService.php b/src/Domain/Application/Auth/Service/AuthLoginPostService.php similarity index 76% rename from src/Domain/Services/Auth/LoginPostService.php rename to src/Domain/Application/Auth/Service/AuthLoginPostService.php index 34b29ec..5cc8368 100644 --- a/src/Domain/Services/Auth/LoginPostService.php +++ b/src/Domain/Application/Auth/Service/AuthLoginPostService.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\Auth; +namespace Phalcon\Api\Domain\Application\Auth\Service; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; @@ -19,7 +19,7 @@ /** * @phpstan-import-type TAuthLoginInput from InputTypes */ -final class LoginPostService extends AbstractAuthService +final class AuthLoginPostService extends AbstractAuthService { /** * @param TAuthLoginInput $input @@ -28,6 +28,6 @@ final class LoginPostService extends AbstractAuthService */ public function __invoke(array $input): Payload { - return $this->facade->authenticate($input, $this->validator); + return $this->facade->authenticate($input); } } diff --git a/src/Domain/Services/Auth/LogoutPostService.php b/src/Domain/Application/Auth/Service/AuthLogoutPostService.php similarity index 76% rename from src/Domain/Services/Auth/LogoutPostService.php rename to src/Domain/Application/Auth/Service/AuthLogoutPostService.php index 2ac289b..7f3594c 100644 --- a/src/Domain/Services/Auth/LogoutPostService.php +++ b/src/Domain/Application/Auth/Service/AuthLogoutPostService.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\Auth; +namespace Phalcon\Api\Domain\Application\Auth\Service; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; @@ -19,7 +19,7 @@ /** * @phpstan-import-type TAuthLogoutInput from InputTypes */ -final class LogoutPostService extends AbstractAuthService +final class AuthLogoutPostService extends AbstractAuthService { /** * @param TAuthLogoutInput $input @@ -28,6 +28,6 @@ final class LogoutPostService extends AbstractAuthService */ public function __invoke(array $input): Payload { - return $this->facade->logout($input, $this->validator); + return $this->facade->logout($input); } } diff --git a/src/Domain/Services/Auth/RefreshPostService.php b/src/Domain/Application/Auth/Service/AuthRefreshPostService.php similarity index 76% rename from src/Domain/Services/Auth/RefreshPostService.php rename to src/Domain/Application/Auth/Service/AuthRefreshPostService.php index 7d50910..3f119e0 100644 --- a/src/Domain/Services/Auth/RefreshPostService.php +++ b/src/Domain/Application/Auth/Service/AuthRefreshPostService.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Services\Auth; +namespace Phalcon\Api\Domain\Application\Auth\Service; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; @@ -19,7 +19,7 @@ /** * @phpstan-import-type TAuthRefreshInput from InputTypes */ -final class RefreshPostService extends AbstractAuthService +final class AuthRefreshPostService extends AbstractAuthService { /** * @param TAuthRefreshInput $input @@ -28,6 +28,6 @@ final class RefreshPostService extends AbstractAuthService */ public function __invoke(array $input): Payload { - return $this->facade->refresh($input, $this->validator); + return $this->facade->refresh($input); } } From deda103294eec409353e985bdac36955bd09caf3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:13:09 -0600 Subject: [PATCH 14/23] [#.x] - added auth command factory --- .../Auth/Command/AuthCommandFactory.php | 88 +++++++++++++++++++ .../Command/AuthCommandFactoryInterface.php | 45 ++++++++++ .../Auth/Command/AuthCommandFactoryTest.php | 87 ++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 src/Domain/Application/Auth/Command/AuthCommandFactory.php create mode 100644 src/Domain/Application/Auth/Command/AuthCommandFactoryInterface.php create mode 100644 tests/Unit/Domain/Application/Auth/Command/AuthCommandFactoryTest.php diff --git a/src/Domain/Application/Auth/Command/AuthCommandFactory.php b/src/Domain/Application/Auth/Command/AuthCommandFactory.php new file mode 100644 index 0000000..d7b47f1 --- /dev/null +++ b/src/Domain/Application/Auth/Command/AuthCommandFactory.php @@ -0,0 +1,88 @@ + + * + * 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\Application\Auth\Command; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interface\SanitizerInterface; + +/** + * @phpstan-import-type TAuthLoginInput from InputTypes + * @phpstan-import-type TAuthLogoutInput from InputTypes + * @phpstan-import-type TAuthRefreshInput from InputTypes + */ +final readonly class AuthCommandFactory implements AuthCommandFactoryInterface +{ + /** + * @param SanitizerInterface $sanitizer + */ + public function __construct( + private SanitizerInterface $sanitizer, + ) { + } + + /** + * @param TAuthLoginInput $input + * + * @return AuthLoginPostCommand + */ + public function authenticate(array $input): AuthLoginPostCommand + { + /** + * Sanitize the input + */ + $dto = AuthInput::new($this->sanitizer, $input); + + /** + * Return the command back + */ + return new AuthLoginPostCommand($dto->email, $dto->password); + } + + /** + * @param TAuthLogoutInput $input + * + * @return AuthLogoutPostCommand + */ + public function logout(array $input): AuthLogoutPostCommand + { + /** + * Sanitize the input + */ + $dto = AuthInput::new($this->sanitizer, $input); + + /** + * Return the command back + */ + return new AuthLogoutPostCommand($dto->token); + } + + /** + * @param TAuthRefreshInput $input + * + * @return AuthRefreshPostCommand + */ + public function refresh(array $input): AuthRefreshPostCommand + { + /** + * Sanitize the input + */ + $dto = AuthInput::new($this->sanitizer, $input); + + /** + * Return the command back + */ + return new AuthRefreshPostCommand($dto->token); + } +} diff --git a/src/Domain/Application/Auth/Command/AuthCommandFactoryInterface.php b/src/Domain/Application/Auth/Command/AuthCommandFactoryInterface.php new file mode 100644 index 0000000..a8dbad8 --- /dev/null +++ b/src/Domain/Application/Auth/Command/AuthCommandFactoryInterface.php @@ -0,0 +1,45 @@ + + * + * 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\Application\Auth\Command; + +use Phalcon\Api\Domain\ADR\InputTypes; + +/** + * @phpstan-import-type TAuthLoginInput from InputTypes + * @phpstan-import-type TAuthLogoutInput from InputTypes + * @phpstan-import-type TAuthRefreshInput from InputTypes + */ +interface AuthCommandFactoryInterface +{ + /** + * @param TAuthLoginInput $input + * + * @return AuthLoginPostCommand + */ + public function authenticate(array $input): AuthLoginPostCommand; + + /** + * @param TAuthLogoutInput $input + * + * @return AuthLogoutPostCommand + */ + public function logout(array $input): AuthLogoutPostCommand; + + /** + * @param TAuthRefreshInput $input + * + * @return AuthRefreshPostCommand + */ + public function refresh(array $input): AuthRefreshPostCommand; +} diff --git a/tests/Unit/Domain/Application/Auth/Command/AuthCommandFactoryTest.php b/tests/Unit/Domain/Application/Auth/Command/AuthCommandFactoryTest.php new file mode 100644 index 0000000..6efb097 --- /dev/null +++ b/tests/Unit/Domain/Application/Auth/Command/AuthCommandFactoryTest.php @@ -0,0 +1,87 @@ + + * + * 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\Application\Auth\Command; + +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactory; +use Phalcon\Api\Domain\Application\Auth\Command\AuthLoginPostCommand; +use Phalcon\Api\Domain\Application\Auth\Command\AuthLogoutPostCommand; +use Phalcon\Api\Domain\Application\Auth\Command\AuthRefreshPostCommand; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizer\AuthSanitizer; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +use function uniqid; + +final class AuthCommandFactoryTest extends AbstractUnitTestCase +{ + public function testAuthenticate(): void + { + $sanitizer = $this->container->get(AuthSanitizer::class); + $factory = new AuthCommandFactory($sanitizer); + + $input = [ + 'email' => 'user@example.com', + 'password' => 's3cr3t', + ]; + + $command = $factory->authenticate($input); + + $this->assertInstanceOf(AuthLoginPostCommand::class, $command); + + $expected = 'user@example.com'; + $actual = $command->email; + $this->assertSame($expected, $actual); + + $expected = 's3cr3t'; + $actual = $command->password; + $this->assertSame($expected, $actual); + } + + public function testLogout(): void + { + $sanitizer = $this->container->get(AuthSanitizer::class); + $factory = new AuthCommandFactory($sanitizer); + + $token = uniqid('token-'); + $input = [ + 'token' => $token, + ]; + + $command = $factory->logout($input); + + $this->assertInstanceOf(AuthLogoutPostCommand::class, $command); + + $expected = $token; + $actual = $command->token; + $this->assertSame($expected, $actual); + } + + public function testRefresh(): void + { + $sanitizer = $this->container->get(AuthSanitizer::class); + $factory = new AuthCommandFactory($sanitizer); + + $token = uniqid('token-'); + $input = [ + 'token' => $token, + ]; + + $command = $factory->refresh($input); + + $this->assertInstanceOf(AuthRefreshPostCommand::class, $command); + + $expected = $token; + $actual = $command->token; + $this->assertSame($expected, $actual); + } +} From 25e07421c8074b55cb7c8113344a7d88f2cbac12 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:13:36 -0600 Subject: [PATCH 15/23] [#.x] - refactored container definitions --- src/Domain/Infrastructure/Container.php | 212 ++++++------------ .../Enums/Container/AuthDefinitionsEnum.php | 116 +++++++--- .../Enums/Container/CommonDefinitionsEnum.php | 69 +++++- .../Container/DefinitionsEnumInterface.php | 33 +++ .../Enums/Container/UserDefinitionsEnum.php | 138 ++++++++---- 5 files changed, 341 insertions(+), 227 deletions(-) create mode 100644 src/Domain/Infrastructure/Enums/Container/DefinitionsEnumInterface.php diff --git a/src/Domain/Infrastructure/Container.php b/src/Domain/Infrastructure/Container.php index 6c290d7..a12e871 100644 --- a/src/Domain/Infrastructure/Container.php +++ b/src/Domain/Infrastructure/Container.php @@ -13,34 +13,22 @@ namespace Phalcon\Api\Domain\Infrastructure; +use Phalcon\Api\Domain\Infrastructure\CommandBus\ContainerHandlerLocator; use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; -use Phalcon\Api\Domain\Infrastructure\Encryption\Security; use Phalcon\Api\Domain\Infrastructure\Enums\Container\AuthDefinitionsEnum; use Phalcon\Api\Domain\Infrastructure\Enums\Container\CommonDefinitionsEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Container\DefinitionsEnumInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Container\UserDefinitionsEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; -use Phalcon\Api\Domain\Infrastructure\Middleware\HealthMiddleware; -use Phalcon\Api\Domain\Infrastructure\Middleware\NotFoundMiddleware; -use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenClaimsMiddleware; -use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenPresenceMiddleware; -use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenRevokedMiddleware; -use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenStructureMiddleware; -use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenUserMiddleware; -use Phalcon\Api\Responder\JsonResponder; use Phalcon\Cache\AdapterFactory; use Phalcon\Cache\Cache; use Phalcon\DataMapper\Pdo\Connection; use Phalcon\Di\Di; use Phalcon\Di\Service; use Phalcon\Filter\FilterFactory; -use Phalcon\Filter\Validation; -use Phalcon\Http\Request; -use Phalcon\Http\Response; use Phalcon\Logger\Adapter\Stream; use Phalcon\Logger\Logger; use Phalcon\Storage\SerializerFactory; -use Phalcon\Support\Registry; use function sprintf; @@ -71,145 +59,52 @@ class Container extends Di /** @var string */ public const APPLICATION = 'application'; /** @var string */ - public const CACHE = 'cache'; - /** @var string */ - public const CONNECTION = 'connection'; - /** @var string */ - public const ENV = 'env'; - /** @var string */ public const EVENTS_MANAGER = 'eventsManager'; /** @var string */ public const FILTER = 'filter'; /** @var string */ - public const JWT_TOKEN = 'jwt.token'; - /** @var string */ - public const JWT_TOKEN_CACHE = 'jwt.token.cache'; - /** @var string */ - public const JWT_TOKEN_MANAGER = 'jwt.token.manager'; - /** @var string */ - public const LOGGER = 'logger'; - /** @var string */ - public const REGISTRY = 'registry'; - /** @var string */ public const REQUEST = 'request'; /** @var string */ public const RESPONSE = 'response'; /** @var string */ public const ROUTER = 'router'; /** @var string */ - public const SECURITY = Security::class; - /** @var string */ public const TIME = 'time'; - /** @var string */ - public const VALIDATION = Validation::class; - /** - * Middleware - */ - public const MIDDLEWARE_HEALTH = HealthMiddleware::class; - public const MIDDLEWARE_NOT_FOUND = NotFoundMiddleware::class; - public const MIDDLEWARE_VALIDATE_TOKEN_CLAIMS = ValidateTokenClaimsMiddleware::class; - public const MIDDLEWARE_VALIDATE_TOKEN_PRESENCE = ValidateTokenPresenceMiddleware::class; - public const MIDDLEWARE_VALIDATE_TOKEN_REVOKED = ValidateTokenRevokedMiddleware::class; - public const MIDDLEWARE_VALIDATE_TOKEN_STRUCTURE = ValidateTokenStructureMiddleware::class; - public const MIDDLEWARE_VALIDATE_TOKEN_USER = ValidateTokenUserMiddleware::class; - /** - * Facades - */ - public const AUTH_FACADE = 'auth.facade'; - public const USER_FACADE = 'user.facade'; - public const USER_FACADE_UPDATE = 'user.facade.update'; - /** - * Services - */ - public const AUTH_LOGIN_POST_SERVICE = 'service.auth.login.post'; - public const AUTH_LOGOUT_POST_SERVICE = 'service.auth.logout.post'; - public const AUTH_REFRESH_POST_SERVICE = 'service.auth.refresh.post'; - public const USER_DELETE_SERVICE = 'service.user.delete'; - public const USER_GET_SERVICE = 'service.user.get'; - public const USER_POST_SERVICE = 'service.user.post'; - public const USER_PUT_SERVICE = 'service.user.put'; - /** - * Mappers - */ - public const USER_MAPPER = UserMapper::class; - /** - * Responders - */ - public const RESPONDER_JSON = JsonResponder::class; - /** - * Repositories - */ - public const USER_REPOSITORY = 'user.repository'; - /** - * Sanitizers - */ - public const AUTH_SANITIZER = 'auth.sanitizer'; - public const USER_SANITIZER = 'user.sanitizer'; - /** - * Validators - */ - public const AUTH_LOGIN_VALIDATOR = 'auth.validator.login'; - public const AUTH_TOKEN_VALIDATOR = 'auth.validator.token'; - public const USER_VALIDATOR = 'user.validator.insert'; - public const USER_VALIDATOR_UPDATE = 'user.validator.update'; public function __construct() { - $this->services = [ - /** - * Base services - */ - self::CACHE => $this->getServiceCache(), - self::CONNECTION => $this->getServiceConnection(), - self::ENV => new Service(EnvManager::class, true), - self::EVENTS_MANAGER => new Service(CommonDefinitionsEnum::EventsManager->definition(), true), - self::FILTER => $this->getServiceFilter(), - self::JWT_TOKEN => new Service(CommonDefinitionsEnum::JWTToken->definition(), true), - self::JWT_TOKEN_CACHE => new Service(CommonDefinitionsEnum::JWTTokenCache->definition(), true), - self::JWT_TOKEN_MANAGER => new Service(CommonDefinitionsEnum::JWTTokenManager->definition(), true), - self::LOGGER => $this->getServiceLogger(), - self::REGISTRY => new Service(Registry::class, true), - self::REQUEST => new Service(Request::class, true), - self::RESPONSE => new Service(Response::class, true), - self::ROUTER => new Service(CommonDefinitionsEnum::Router->definition(), true), - - /** - * Facades - */ - self::AUTH_FACADE => new Service(AuthDefinitionsEnum::AuthFacade->definition()), - self::USER_FACADE => new Service(UserDefinitionsEnum::UserFacade->definition()), - self::USER_FACADE_UPDATE => new Service(UserDefinitionsEnum::UserFacadeUpdate->definition()), - - /** - * Repositories - */ - self::USER_REPOSITORY => new Service(UserDefinitionsEnum::UserRepository->definition()), - - /** - * Sanitizers - */ - self::AUTH_SANITIZER => new Service(AuthDefinitionsEnum::AuthSanitizer->definition()), - self::USER_SANITIZER => new Service(UserDefinitionsEnum::UserSanitizer->definition()), - - /** - * Validators - */ - self::AUTH_LOGIN_VALIDATOR => new Service(AuthDefinitionsEnum::AuthLoginValidator->definition()), - self::AUTH_TOKEN_VALIDATOR => new Service(AuthDefinitionsEnum::AuthTokenValidator->definition()), - self::USER_VALIDATOR => new Service(UserDefinitionsEnum::UserValidator->definition()), - self::USER_VALIDATOR_UPDATE => new Service(UserDefinitionsEnum::UserValidatorUpdate->definition()), - - /** - * Services - */ - self::AUTH_LOGIN_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthLoginPost->definition()), - self::AUTH_LOGOUT_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthLogoutPost->definition()), - self::AUTH_REFRESH_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthRefreshPost->definition()), - self::USER_DELETE_SERVICE => new Service(UserDefinitionsEnum::UserDelete->definition()), - self::USER_GET_SERVICE => new Service(UserDefinitionsEnum::UserGet->definition()), - self::USER_POST_SERVICE => new Service(UserDefinitionsEnum::UserPost->definition()), - self::USER_PUT_SERVICE => new Service(UserDefinitionsEnum::UserPut->definition()), + /** + * Trying to keep this as clean and as configurable as possible. + * + * The enumerations that hold the definition for each service, also + * keep the sharable state (shared service or not) and also reference + * this file's constants for their values where necessary. Therefore, + * we can register the services with a simple loop for each enumeration. + * + * + * Base services + */ + $services = [ + Cache::class => $this->getServiceCache(), + Connection::class => $this->getServiceConnection(), + self::FILTER => $this->getServiceFilter(), + Logger::class => $this->getServiceLogger(), + ContainerHandlerLocator::class => $this->getServiceHandlerLocator(), ]; + /** + * Common services + */ + $services = $this->registerFromEnum(CommonDefinitionsEnum::class, $services); + /** + * Auth related services + */ + $services = $this->registerFromEnum(AuthDefinitionsEnum::class, $services); + /** + * User related services + */ + $services = $this->registerFromEnum(UserDefinitionsEnum::class, $services); + + $this->services = $services; parent::__construct(); } @@ -222,7 +117,7 @@ private function getServiceCache(): Service return new Service( function () { /** @var EnvManager $env */ - $env = $this->getShared(self::ENV); + $env = $this->getShared(EnvManager::class); /** @var string $prefix */ $prefix = $env->get('CACHE_PREFIX', '-rest-'); @@ -265,7 +160,7 @@ private function getServiceConnection(): Service return new Service( function () { /** @var EnvManager $env */ - $env = $this->getShared(self::ENV); + $env = $this->getShared(EnvManager::class); /** @var string $dbName */ $dbName = $env->get('DB_NAME', 'phalcon'); @@ -313,6 +208,18 @@ function () { ); } + /** + * @return Service + */ + private function getServiceHandlerLocator(): Service + { + return new Service( + function () { + return new ContainerHandlerLocator($this); + } + ); + } + /** * @return Service */ @@ -321,7 +228,7 @@ private function getServiceLogger(): Service return new Service( function () { /** @var EnvManager $env */ - $env = $this->getShared(self::ENV); + $env = $this->getShared(EnvManager::class); /** @var string $logName */ $logName = $env->get('LOG_FILENAME', 'rest-api'); @@ -338,4 +245,27 @@ function () { } ); } + + /** + * @param string $enum + * @param array $services + * + * @return array + */ + private function registerFromEnum(string $enum, array $services): array + { + /** @var DefinitionsEnumInterface[] $items */ + $items = $enum::cases(); + foreach ($items as $definition) { + /** @var string $serviceName */ + $serviceName = $definition->value; + + $services[$serviceName] = new Service( + $definition->definition(), + $definition->isShared() + ); + } + + return $services; + } } diff --git a/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php index 12a0f0a..820ccbe 100644 --- a/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php @@ -13,27 +13,40 @@ namespace Phalcon\Api\Domain\Infrastructure\Enums\Container; +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactory; +use Phalcon\Api\Domain\Application\Auth\Facade\AuthFacade; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthLoginPostHandler; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthLogoutPostHandler; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthRefreshPostHandler; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLoginPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthLogoutPostService; +use Phalcon\Api\Domain\Application\Auth\Service\AuthRefreshPostService; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandBus; use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Facades\AuthFacade; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthLoginValidator; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthTokenValidator; -use Phalcon\Api\Domain\Services\Auth\LoginPostService; -use Phalcon\Api\Domain\Services\Auth\LogoutPostService; -use Phalcon\Api\Domain\Services\Auth\RefreshPostService; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizer\AuthSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validator\AuthLoginValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validator\AuthTokenValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Filter\Validation; /** * @phpstan-import-type TService from Container */ -enum AuthDefinitionsEnum +enum AuthDefinitionsEnum: string implements DefinitionsEnumInterface { - case AuthLoginPost; - case AuthLogoutPost; - case AuthRefreshPost; - case AuthFacade; - case AuthSanitizer; - case AuthLoginValidator; - case AuthTokenValidator; + case AuthCommandFactory = AuthCommandFactory::class; + case AuthLoginPost = AuthLoginPostService::class; + case AuthLogoutPost = AuthLogoutPostService::class; + case AuthRefreshPost = AuthRefreshPostService::class; + case AuthFacade = AuthFacade::class; + case AuthLoginHandler = AuthLoginPostHandler::class; + case AuthLogoutHandler = AuthLogoutPostHandler::class; + case AuthRefreshHandler = AuthRefreshPostHandler::class; + case AuthSanitizer = AuthSanitizer::class; + case AuthLoginValidator = AuthLoginValidator::class; + case AuthTokenValidator = AuthTokenValidator::class; /** * @return TService @@ -41,42 +54,54 @@ enum AuthDefinitionsEnum public function definition(): array { return match ($this) { - self::AuthLoginPost => [ - 'className' => LoginPostService::class, + self::AuthCommandFactory => [ + 'className' => AuthCommandFactory::class, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::AUTH_FACADE, + 'name' => AuthSanitizer::class, + ], + ], + ], + self::AuthLoginPost => $this->getService(AuthLoginPostService::class), + self::AuthLogoutPost => $this->getService(AuthLogoutPostService::class), + self::AuthRefreshPost => $this->getService(AuthRefreshPostService::class), + self::AuthFacade => [ + 'className' => AuthFacade::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => CommandBus::class, ], [ 'type' => 'service', - 'name' => Container::AUTH_LOGIN_VALIDATOR, + 'name' => AuthCommandFactory::class, ], ], ], - self::AuthLogoutPost => $this->getService(LogoutPostService::class), - self::AuthRefreshPost => $this->getService(RefreshPostService::class), - self::AuthFacade => [ - 'className' => AuthFacade::class, + self::AuthLoginHandler => [ + 'className' => AuthLoginPostHandler::class, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::USER_REPOSITORY, + 'name' => UserRepository::class, ], [ 'type' => 'service', - 'name' => Container::AUTH_SANITIZER, + 'name' => TokenManager::class, ], [ 'type' => 'service', - 'name' => Container::JWT_TOKEN_MANAGER, + 'name' => Security::class, ], [ 'type' => 'service', - 'name' => Container::SECURITY, + 'name' => AuthLoginValidator::class, ], ], ], + self::AuthLogoutHandler => $this->getHandlerService(AuthLogoutPostHandler::class), + self::AuthRefreshHandler => $this->getHandlerService(AuthRefreshPostHandler::class), self::AuthSanitizer => [ 'className' => AuthSanitizer::class, 'arguments' => [ @@ -91,7 +116,7 @@ public function definition(): array 'arguments' => [ [ 'type' => 'service', - 'name' => Container::VALIDATION, + 'name' => Validation::class, ], ], ], @@ -100,36 +125,59 @@ public function definition(): array 'arguments' => [ [ 'type' => 'service', - 'name' => Container::JWT_TOKEN_MANAGER, + 'name' => TokenManager::class, ], [ 'type' => 'service', - 'name' => Container::USER_REPOSITORY, + 'name' => UserRepository::class, ], [ 'type' => 'service', - 'name' => Container::VALIDATION, + 'name' => Validation::class, ], ], ], }; } + public function isShared(): bool + { + return false; + } + /** + * @param string $className + * * @return TService */ - private function getService(string $className): array + private function getHandlerService(string $className): array { return [ 'className' => $className, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::AUTH_FACADE, + 'name' => TokenManager::class, ], [ 'type' => 'service', - 'name' => Container::AUTH_TOKEN_VALIDATOR, + 'name' => AuthTokenValidator::class, + ], + ], + ]; + } + + /** + * @return TService + */ + private function getService(string $className): array + { + return [ + 'className' => $className, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => AuthFacade::class, ], ], ]; diff --git a/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php index c3f426f..c891497 100644 --- a/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php @@ -13,23 +13,37 @@ namespace Phalcon\Api\Domain\Infrastructure\Enums\Container; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandBus; +use Phalcon\Api\Domain\Infrastructure\CommandBus\ContainerHandlerLocator; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCache; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Responder\JsonResponder; +use Phalcon\Cache\Cache; use Phalcon\Events\Manager as EventsManager; +use Phalcon\Http\Request; +use Phalcon\Http\Response; use Phalcon\Mvc\Router; +use Phalcon\Support\Registry; /** * @phpstan-import-type TService from Container */ -enum CommonDefinitionsEnum +enum CommonDefinitionsEnum: string implements DefinitionsEnumInterface { - case EventsManager; - case JWTToken; - case JWTTokenCache; - case JWTTokenManager; - case Router; + case CommandBus = CommandBus::class; + case EventsManager = Container::EVENTS_MANAGER; + case EnvManager = EnvManager::class; + case JsonResponder = JsonResponder::class; + case JWTToken = JWTToken::class; + case JWTTokenCache = TokenCache::class; + case JWTTokenManager = TokenManager::class; + case Registry = Registry::class; + case Request = Container::REQUEST; + case Response = Container::RESPONSE; + case Router = Container::ROUTER; /** * @return TService @@ -37,6 +51,15 @@ enum CommonDefinitionsEnum public function definition(): array { return match ($this) { + self::CommandBus => [ + 'className' => CommandBus::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => ContainerHandlerLocator::class, + ], + ], + ], self::EventsManager => [ 'className' => EventsManager::class, 'calls' => [ @@ -51,12 +74,18 @@ public function definition(): array ], ], ], + self::EnvManager => [ + 'className' => EnvManager::class, + ], + self::JsonResponder => [ + 'className' => JsonResponder::class, + ], self::JWTToken => [ 'className' => JWTToken::class, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::ENV, + 'name' => EnvManager::class, ], ], ], @@ -65,7 +94,7 @@ public function definition(): array 'arguments' => [ [ 'type' => 'service', - 'name' => Container::CACHE, + 'name' => Cache::class, ], ], ], @@ -74,18 +103,27 @@ public function definition(): array 'arguments' => [ [ 'type' => 'service', - 'name' => Container::JWT_TOKEN_CACHE, + 'name' => TokenCache::class, ], [ 'type' => 'service', - 'name' => Container::ENV, + 'name' => EnvManager::class, ], [ 'type' => 'service', - 'name' => Container::JWT_TOKEN, + 'name' => JWTToken::class, ], ], ], + self::Registry => [ + 'className' => Registry::class, + ], + self::Request => [ + 'className' => Request::class, + ], + self::Response => [ + 'className' => Response::class, + ], self::Router => [ 'className' => Router::class, 'arguments' => [ @@ -97,4 +135,13 @@ public function definition(): array ] }; } + + public function isShared(): bool + { + return match ($this) { + self::JWTToken, + self::JWTTokenCache => false, + default => true, + }; + } } diff --git a/src/Domain/Infrastructure/Enums/Container/DefinitionsEnumInterface.php b/src/Domain/Infrastructure/Enums/Container/DefinitionsEnumInterface.php new file mode 100644 index 0000000..5afec2d --- /dev/null +++ b/src/Domain/Infrastructure/Enums/Container/DefinitionsEnumInterface.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\Infrastructure\Enums\Container; + +use BackedEnum; +use Phalcon\Api\Domain\Infrastructure\Container; + +/** + * @phpstan-import-type TService from Container + */ +interface DefinitionsEnumInterface extends BackedEnum +{ + /** + * @return TService + */ + public function definition(): array; + + /** + * @return bool + */ + public function isShared(): bool; +} diff --git a/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php index 38e3565..c741396 100644 --- a/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php @@ -13,32 +13,48 @@ namespace Phalcon\Api\Domain\Infrastructure\Enums\Container; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Application\User\Facade\UserFacade; +use Phalcon\Api\Domain\Application\User\Handler\UserDeleteHandler; +use Phalcon\Api\Domain\Application\User\Handler\UserGetHandler; +use Phalcon\Api\Domain\Application\User\Handler\UserPostHandler; +use Phalcon\Api\Domain\Application\User\Handler\UserPutHandler; +use Phalcon\Api\Domain\Application\User\Service\UserDeleteService; +use Phalcon\Api\Domain\Application\User\Service\UserGetService; +use Phalcon\Api\Domain\Application\User\Service\UserPostService; +use Phalcon\Api\Domain\Application\User\Service\UserPutService; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandBus; use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Facades\UserFacade; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidator; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidatorUpdate; -use Phalcon\Api\Domain\Services\User\UserDeleteService; -use Phalcon\Api\Domain\Services\User\UserGetService; -use Phalcon\Api\Domain\Services\User\UserPostService; -use Phalcon\Api\Domain\Services\User\UserPutService; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizer\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validator\UserValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validator\UserValidatorUpdate; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\DataMapper\Pdo\Connection; +use Phalcon\Filter\Validation; +use Phalcon\Support\Registry; /** * @phpstan-import-type TService from Container */ -enum UserDefinitionsEnum +enum UserDefinitionsEnum: string implements DefinitionsEnumInterface { - case UserDelete; - case UserGet; - case UserPost; - case UserPut; - case UserFacade; - case UserFacadeUpdate; - case UserRepository; - case UserSanitizer; - case UserValidator; - case UserValidatorUpdate; + case UserCommandFactory = UserCommandFactory::class; + case UserDelete = UserDeleteService::class; + case UserGet = UserGetService::class; + case UserPost = UserPostService::class; + case UserPut = UserPutService::class; + case UserFacade = UserFacade::class; + case UserDeleteHandler = UserDeleteHandler::class; + case UserGetHandler = UserGetHandler::class; + case UserPostHandler = UserPostHandler::class; + case UserPutHandler = UserPutHandler::class; + case UserMapper = UserMapper::class; + case UserRepository = UserRepository::class; + case UserSanitizer = UserSanitizer::class; + case UserValidator = UserValidator::class; + case UserValidatorUpdate = UserValidatorUpdate::class; /** * @return TService @@ -46,78 +62,113 @@ enum UserDefinitionsEnum public function definition(): array { return match ($this) { + self::UserCommandFactory => [ + 'className' => UserCommandFactory::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => UserSanitizer::class, + ], + ], + ], self::UserDelete => $this->getService(UserDeleteService::class), self::UserGet => $this->getService(UserGetService::class), self::UserPost => $this->getService(UserPostService::class), - self::UserPut => [ - 'className' => UserPutService::class, + self::UserPut => $this->getService(UserPutService::class), + self::UserFacade => [ + 'className' => UserFacade::class, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::USER_FACADE_UPDATE, + 'name' => CommandBus::class, + ], + [ + 'type' => 'service', + 'name' => UserCommandFactory::class, ], ], ], - self::UserFacade => [ - 'className' => UserFacade::class, + self::UserDeleteHandler => [ + 'className' => UserDeleteHandler::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => UserRepository::class, + ], + ], + ], + self::UserGetHandler => [ + 'className' => UserGetHandler::class, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::USER_SANITIZER, + 'name' => UserRepository::class, ], + ], + ], + self::UserPostHandler => [ + 'className' => UserPostHandler::class, + 'arguments' => [ [ 'type' => 'service', - 'name' => Container::USER_VALIDATOR, + 'name' => UserValidator::class, ], [ 'type' => 'service', - 'name' => Container::USER_MAPPER, + 'name' => UserMapper::class, ], [ 'type' => 'service', - 'name' => Container::USER_REPOSITORY, + 'name' => UserRepository::class, ], [ 'type' => 'service', - 'name' => Container::SECURITY, + 'name' => Registry::class, + ], + [ + 'type' => 'service', + 'name' => Security::class, ], ], ], - self::UserFacadeUpdate => [ - 'className' => UserFacade::class, + self::UserPutHandler => [ + 'className' => UserPutHandler::class, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::USER_SANITIZER, + 'name' => UserValidatorUpdate::class, ], [ 'type' => 'service', - 'name' => Container::USER_VALIDATOR_UPDATE, + 'name' => UserMapper::class, ], [ 'type' => 'service', - 'name' => Container::USER_MAPPER, + 'name' => UserRepository::class, ], [ 'type' => 'service', - 'name' => Container::USER_REPOSITORY, + 'name' => Registry::class, ], [ 'type' => 'service', - 'name' => Container::SECURITY, + 'name' => Security::class, ], ], ], + self::UserMapper => [ + 'className' => UserMapper::class, + ], self::UserRepository => [ 'className' => UserRepository::class, 'arguments' => [ [ 'type' => 'service', - 'name' => Container::CONNECTION, + 'name' => Connection::class, ], [ 'type' => 'service', - 'name' => Container::USER_MAPPER, + 'name' => UserMapper::class, ], ], ], @@ -135,6 +186,11 @@ public function definition(): array }; } + public function isShared(): bool + { + return false; + } + /** * @param class-string $className * @@ -147,7 +203,7 @@ private function getService(string $className): array 'arguments' => [ [ 'type' => 'service', - 'name' => Container::USER_FACADE, + 'name' => UserFacade::class, ], ], ]; @@ -165,7 +221,7 @@ private function getServiceValidator(string $className): array 'arguments' => [ [ 'type' => 'service', - 'name' => Container::VALIDATION, + 'name' => Validation::class, ], ], ]; From fce538ec54e7f25a74cbdd5a9fca5a4a8e5a8e50 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:14:03 -0600 Subject: [PATCH 16/23] [#.x] - refactored facades to use the bus --- .../Application/Auth/Facade/AuthFacade.php | 72 +++++++ .../Application/User/Facade/UserFacade.php | 94 +++++++++ .../DataSource/Auth/Facades/AuthFacade.php | 194 ------------------ 3 files changed, 166 insertions(+), 194 deletions(-) create mode 100644 src/Domain/Application/Auth/Facade/AuthFacade.php create mode 100644 src/Domain/Application/User/Facade/UserFacade.php delete mode 100644 src/Domain/Infrastructure/DataSource/Auth/Facades/AuthFacade.php diff --git a/src/Domain/Application/Auth/Facade/AuthFacade.php b/src/Domain/Application/Auth/Facade/AuthFacade.php new file mode 100644 index 0000000..c32dffb --- /dev/null +++ b/src/Domain/Application/Auth/Facade/AuthFacade.php @@ -0,0 +1,72 @@ + + * + * 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\Application\Auth\Facade; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactoryInterface; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandBusInterface; + +/** + * @phpstan-import-type TAuthLoginInput from InputTypes + * @phpstan-import-type TAuthLogoutInput from InputTypes + * @phpstan-import-type TAuthRefreshInput from InputTypes + */ +final readonly class AuthFacade +{ + /** + * @param CommandBusInterface $bus + */ + public function __construct( + private CommandBusInterface $bus, + private AuthCommandFactoryInterface $factory + ) { + } + + /** + * Authenticates users (login) + * + * @param TAuthLoginInput $input + * + * @return Payload + */ + public function authenticate(array $input): Payload + { + return $this->bus->dispatch($this->factory->authenticate($input)); + } + + /** + * Logout: revoke refresh token after parsing/validation. + * + * @param TAuthLogoutInput $input + * + * @return Payload + */ + public function logout(array $input): Payload + { + return $this->bus->dispatch($this->factory->logout($input)); + } + + /** + * Refresh: validate refresh token, issue new tokens via TokenManager. + * + * @param TAuthLogoutInput $input + * + * @return Payload + */ + public function refresh(array $input): Payload + { + return $this->bus->dispatch($this->factory->refresh($input)); + } +} diff --git a/src/Domain/Application/User/Facade/UserFacade.php b/src/Domain/Application/User/Facade/UserFacade.php new file mode 100644 index 0000000..6f10a7b --- /dev/null +++ b/src/Domain/Application/User/Facade/UserFacade.php @@ -0,0 +1,94 @@ + + * + * 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\Application\User\Facade; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactoryInterface; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandBusInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; + +/** + * Orchestration for workflow + * + * - Sanitization + * - DTO creation + * - Validation + * - Pre-operation checks (when necessary) + * - Repository operation + * + * @phpstan-import-type TUserInput from InputTypes + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + * @phpstan-import-type TUserDbRecordOptional from UserTypes + */ +final readonly class UserFacade +{ + /** + * @param CommandBusInterface $bus + * @param UserCommandFactoryInterface $factory + */ + public function __construct( + private CommandBusInterface $bus, + private UserCommandFactoryInterface $factory, + ) { + } + + /** + * Delete a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function delete(array $input): Payload + { + return $this->bus->dispatch($this->factory->delete($input)); + } + + /** + * Get a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function get(array $input): Payload + { + return $this->bus->dispatch($this->factory->get($input)); + } + + /** + * Create a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function insert(array $input): Payload + { + return $this->bus->dispatch($this->factory->insert($input)); + } + + /** + * Create a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function update(array $input): Payload + { + return $this->bus->dispatch($this->factory->update($input)); + } +} diff --git a/src/Domain/Infrastructure/DataSource/Auth/Facades/AuthFacade.php b/src/Domain/Infrastructure/DataSource/Auth/Facades/AuthFacade.php deleted file mode 100644 index c4f24a8..0000000 --- a/src/Domain/Infrastructure/DataSource/Auth/Facades/AuthFacade.php +++ /dev/null @@ -1,194 +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\Domain\Infrastructure\DataSource\Auth\Facades; - -use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\ADR\Payload; -use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; -use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; -use Phalcon\Api\Domain\Infrastructure\Encryption\Security; -use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; -use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; - -/** - * @phpstan-import-type TAuthLoginInput from InputTypes - * @phpstan-import-type TAuthLogoutInput from InputTypes - * @phpstan-import-type TAuthRefreshInput from InputTypes - */ -final class AuthFacade -{ - /** - * @param UserRepositoryInterface $repository - * @param SanitizerInterface $sanitizer - * @param TokenManagerInterface $tokenManager - * @param Security $security - */ - public function __construct( - private readonly UserRepositoryInterface $repository, - private readonly SanitizerInterface $sanitizer, - private readonly TokenManagerInterface $tokenManager, - private readonly Security $security, - ) { - } - - /** - * Authenticates users (login) - * - * @param TAuthLoginInput $input - * @param ValidatorInterface $validator - * - * @return Payload - */ - public function authenticate( - array $input, - ValidatorInterface $validator - ): Payload { - /** - * Data Transfer Object - */ - $dto = AuthInput::new($this->sanitizer, $input); - - /** - * Validate - */ - $validation = $validator->validate($dto); - if (!$validation->isValid()) { - return Payload::unauthorized($validation->getErrors()); - } - - /** - * Find the user by email - */ - /** @var string $email */ - $email = $dto->email; - $domainUser = $this->repository->findByEmail($email); - if (null === $domainUser) { - return Payload::unauthorized([HttpCodesEnum::AppIncorrectCredentials->error()]); - } - - /** - * Verify the password - */ - /** @var string $suppliedPassword */ - $suppliedPassword = $dto->password; - /** @var string $dbPassword */ - $dbPassword = $domainUser->password; - if (true !== $this->security->verify($suppliedPassword, $dbPassword)) { - return Payload::unauthorized([HttpCodesEnum::AppIncorrectCredentials->error()]); - } - - /** - * Issue a new set of tokens - */ - $tokens = $this->tokenManager->issue($domainUser); - - /** - * Construct the response - */ - $results = [ - 'authenticated' => true, - 'user' => [ - 'id' => $domainUser->id, - 'name' => $domainUser->fullName(), - 'email' => $domainUser->email, - ], - 'jwt' => [ - 'token' => $tokens['token'], - 'refreshToken' => $tokens['refreshToken'], - ], - ]; - - return Payload::success($results); - } - - /** - * Logout: revoke refresh token after parsing/validation. - * - * @param TAuthLogoutInput $input - * @param ValidatorInterface $validator - * - * @return Payload - */ - public function logout( - array $input, - ValidatorInterface $validator - ): Payload { - /** - * Data Transfer Object - */ - $dto = AuthInput::new($this->sanitizer, $input); - - /** - * Validate - */ - $validation = $validator->validate($dto); - if (!$validation->isValid()) { - return Payload::unauthorized($validation->getErrors()); - } - - /** - * If we are here validation has passed and the Result object - * has the user in the meta store - */ - /** @var User $domainUser */ - $domainUser = $validation->getMeta('user'); - - $this->tokenManager->revoke($domainUser); - - return Payload::success(['authenticated' => false]); - } - - /** - * Refresh: validate refresh token, issue new tokens via TokenManager. - * - * @param TAuthLogoutInput $input - * @param ValidatorInterface $validator - * - * @return Payload - */ - public function refresh( - array $input, - ValidatorInterface $validator - ): Payload { - /** - * Data Transfer Object - */ - $dto = AuthInput::new($this->sanitizer, $input); - - /** - * Validate - */ - $validation = $validator->validate($dto); - if (!$validation->isValid()) { - return Payload::unauthorized($validation->getErrors()); - } - - /** - * If we are here validation has passed and the Result object - * has the user in the meta store - */ - /** @var User $domainUser */ - $domainUser = $validation->getMeta('user'); - - $tokens = $this->tokenManager->refresh($domainUser); - - return Payload::success([ - 'token' => $tokens['token'], - 'refreshToken' => $tokens['refreshToken'], - ]); - } -} From faba40e76eefdc967778ca2880555ccda579e78c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:14:42 -0600 Subject: [PATCH 17/23] [#.x] - added auth handlers --- .../AbstractAuthLogoutRefreshHandler.php | 35 ++ .../Auth/Handler/AuthLoginPostHandler.php | 108 ++++++ .../Auth/Handler/AuthLogoutPostHandler.php | 54 +++ .../Auth/Handler/AuthRefreshPostHandler.php | 57 ++++ .../Handler/AbstractUserPutPostHandler.php | 93 +++++ .../Auth/Handler/LoginPostHandlerTest.php | 182 ++++++++++ .../Auth/Handler/LogoutPostHandlerTest.php | 140 ++++++++ .../Auth/Handler/RefreshPostHandlerTest.php | 320 ++++++++++++++++++ 8 files changed, 989 insertions(+) create mode 100644 src/Domain/Application/Auth/Handler/AbstractAuthLogoutRefreshHandler.php create mode 100644 src/Domain/Application/Auth/Handler/AuthLoginPostHandler.php create mode 100644 src/Domain/Application/Auth/Handler/AuthLogoutPostHandler.php create mode 100644 src/Domain/Application/Auth/Handler/AuthRefreshPostHandler.php create mode 100644 src/Domain/Application/User/Handler/AbstractUserPutPostHandler.php create mode 100644 tests/Unit/Domain/Application/Auth/Handler/LoginPostHandlerTest.php create mode 100644 tests/Unit/Domain/Application/Auth/Handler/LogoutPostHandlerTest.php create mode 100644 tests/Unit/Domain/Application/Auth/Handler/RefreshPostHandlerTest.php diff --git a/src/Domain/Application/Auth/Handler/AbstractAuthLogoutRefreshHandler.php b/src/Domain/Application/Auth/Handler/AbstractAuthLogoutRefreshHandler.php new file mode 100644 index 0000000..9b190da --- /dev/null +++ b/src/Domain/Application/Auth/Handler/AbstractAuthLogoutRefreshHandler.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\Application\Auth\Handler; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Infrastructure\CommandBus\HandlerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; + +/** + * @phpstan-import-type TAuthLogoutInput from InputTypes + */ +abstract class AbstractAuthLogoutRefreshHandler implements HandlerInterface +{ + /** + * @param TokenManagerInterface $tokenManager + * @param ValidatorInterface $validator + */ + public function __construct( + protected readonly TokenManagerInterface $tokenManager, + protected readonly ValidatorInterface $validator, + ) { + } +} diff --git a/src/Domain/Application/Auth/Handler/AuthLoginPostHandler.php b/src/Domain/Application/Auth/Handler/AuthLoginPostHandler.php new file mode 100644 index 0000000..0f7365b --- /dev/null +++ b/src/Domain/Application/Auth/Handler/AuthLoginPostHandler.php @@ -0,0 +1,108 @@ + + * + * 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\Application\Auth\Handler; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Application\Auth\Command\AuthLoginPostCommand; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\CommandBus\HandlerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; + +/** + * @phpstan-import-type TAuthLoginInput from InputTypes + */ +final class AuthLoginPostHandler implements HandlerInterface +{ + /** + * @param UserRepositoryInterface $repository + * @param TokenManagerInterface $tokenManager + * @param Security $security + * @param ValidatorInterface $validator + */ + public function __construct( + private readonly UserRepositoryInterface $repository, + private readonly TokenManagerInterface $tokenManager, + private readonly Security $security, + private readonly ValidatorInterface $validator + ) { + } + + /** + * Authenticates users (login) + * + * @param CommandInterface $command + * + * @return Payload + */ + public function __invoke(CommandInterface $command): Payload + { + /** + * Validate + */ + /** @var AuthLoginPostCommand $command */ + $validation = $this->validator->validate($command); + if (!$validation->isValid()) { + return Payload::unauthorized($validation->getErrors()); + } + + /** + * Find the user by email + */ + /** @var string $email */ + $email = $command->email; + $domainUser = $this->repository->findByEmail($email); + if (null === $domainUser) { + return Payload::unauthorized([HttpCodesEnum::AppIncorrectCredentials->error()]); + } + + /** + * Verify the password + */ + /** @var string $suppliedPassword */ + $suppliedPassword = $command->password; + /** @var string $dbPassword */ + $dbPassword = $domainUser->password; + if (true !== $this->security->verify($suppliedPassword, $dbPassword)) { + return Payload::unauthorized([HttpCodesEnum::AppIncorrectCredentials->error()]); + } + + /** + * Issue a new set of tokens + */ + $tokens = $this->tokenManager->issue($domainUser); + + /** + * Construct the response + */ + $results = [ + 'authenticated' => true, + 'user' => [ + 'id' => $domainUser->id, + 'name' => $domainUser->fullName(), + 'email' => $domainUser->email, + ], + 'jwt' => [ + 'token' => $tokens['token'], + 'refreshToken' => $tokens['refreshToken'], + ], + ]; + + return Payload::success($results); + } +} diff --git a/src/Domain/Application/Auth/Handler/AuthLogoutPostHandler.php b/src/Domain/Application/Auth/Handler/AuthLogoutPostHandler.php new file mode 100644 index 0000000..26f1dc7 --- /dev/null +++ b/src/Domain/Application/Auth/Handler/AuthLogoutPostHandler.php @@ -0,0 +1,54 @@ + + * + * 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\Application\Auth\Handler; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; + +/** + * @phpstan-import-type TAuthLogoutInput from InputTypes + */ +final class AuthLogoutPostHandler extends AbstractAuthLogoutRefreshHandler +{ + /** + * Logout: revoke refresh token after parsing/validation. + * + * @param CommandInterface $command + * + * @return Payload + */ + public function __invoke(CommandInterface $command): Payload + { + /** + * Validate + */ + $validation = $this->validator->validate($command); + if (!$validation->isValid()) { + return Payload::unauthorized($validation->getErrors()); + } + + /** + * If we are here validation has passed and the Result object + * has the user in the meta store + */ + /** @var User $domainUser */ + $domainUser = $validation->getMeta('user'); + + $this->tokenManager->revoke($domainUser); + + return Payload::success(['authenticated' => false]); + } +} diff --git a/src/Domain/Application/Auth/Handler/AuthRefreshPostHandler.php b/src/Domain/Application/Auth/Handler/AuthRefreshPostHandler.php new file mode 100644 index 0000000..be368c1 --- /dev/null +++ b/src/Domain/Application/Auth/Handler/AuthRefreshPostHandler.php @@ -0,0 +1,57 @@ + + * + * 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\Application\Auth\Handler; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; + +/** + * @phpstan-import-type TAuthRefreshInput from InputTypes + */ +final class AuthRefreshPostHandler extends AbstractAuthLogoutRefreshHandler +{ + /** + * Refresh: validate refresh token, issue new tokens via TokenManager. + * + * @param CommandInterface $command + * + * @return Payload + */ + public function __invoke(CommandInterface $command): Payload + { + /** + * Validate + */ + $validation = $this->validator->validate($command); + if (!$validation->isValid()) { + return Payload::unauthorized($validation->getErrors()); + } + + /** + * If we are here validation has passed and the Result object + * has the user in the meta store + */ + /** @var User $domainUser */ + $domainUser = $validation->getMeta('user'); + + $tokens = $this->tokenManager->refresh($domainUser); + + return Payload::success([ + 'token' => $tokens['token'], + 'refreshToken' => $tokens['refreshToken'], + ]); + } +} diff --git a/src/Domain/Application/User/Handler/AbstractUserPutPostHandler.php b/src/Domain/Application/User/Handler/AbstractUserPutPostHandler.php new file mode 100644 index 0000000..aa89c39 --- /dev/null +++ b/src/Domain/Application/User/Handler/AbstractUserPutPostHandler.php @@ -0,0 +1,93 @@ + + * + * 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\Application\User\Handler; + +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Infrastructure\CommandBus\HandlerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interface\MapperInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Support\Registry; + +use function array_filter; + +/** + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + * @phpstan-import-type TUserDbRecordOptional from UserTypes + */ +abstract class AbstractUserPutPostHandler implements HandlerInterface +{ + /** + * @param ValidatorInterface $validator + * @param MapperInterface $mapper + * @param UserRepositoryInterface $repository + * @param Security $security + */ + public function __construct( + protected readonly ValidatorInterface $validator, + protected readonly MapperInterface $mapper, + protected readonly UserRepositoryInterface $repository, + protected readonly Registry $registry, + private readonly Security $security, + ) { + } + + /** + * @param TUserDbRecordOptional $row + * + * @return TUserDbRecordOptional + */ + protected function cleanupFields(array $row): array + { + unset($row['usr_id']); + + return array_filter( + $row, + static fn($v) => $v !== null && $v !== '' + ); + } + + /** + * @param HttpCodesEnum $item + * @param string $message + * + * @return Payload + */ + protected function getErrorPayload( + HttpCodesEnum $item, + string $message + ): Payload { + return Payload::error([[$item->text() . $message]]); + } + + /** + * @param TUserDomainToDbRecord $input + * + * @return TUserDomainToDbRecord + */ + protected function processPassword(array $input): array + { + if (null !== $input['usr_password']) { + $plain = $input['usr_password']; + $hashed = $this->security->hash($plain); + + $input['usr_password'] = $hashed; + } + + return $input; + } +} diff --git a/tests/Unit/Domain/Application/Auth/Handler/LoginPostHandlerTest.php b/tests/Unit/Domain/Application/Auth/Handler/LoginPostHandlerTest.php new file mode 100644 index 0000000..99285e0 --- /dev/null +++ b/tests/Unit/Domain/Application/Auth/Handler/LoginPostHandlerTest.php @@ -0,0 +1,182 @@ + + * + * 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\Application\Auth\Handler; + +use Faker\Factory; +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactory; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthLoginPostHandler; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; + +final class LoginPostHandlerTest extends AbstractUnitTestCase +{ + public function testHandlerEmptyCredentials(): void + { + /** @var AuthLoginPostHandler $handler */ + $handler = $this->container->get(AuthLoginPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $command = $factory->authenticate([]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppIncorrectCredentials->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } + + public function testHandlerWithCredentials(): void + { + /** @var AuthLoginPostHandler $handler */ + $handler = $this->container->get(AuthLoginPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $migration = new UsersMigration($this->getConnection()); + + /** + * Setting the password to something we know + */ + $password = 'password'; + + $dbUser = $this->getNewUser($migration, ['usr_password' => $password]); + $email = $dbUser['usr_email']; + $payload = [ + 'email' => $email, + 'password' => $password, + ]; + + $command = $factory->authenticate($payload); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $this->assertArrayHasKey('authenticated', $data); + $this->assertArrayHasKey('user', $data); + $this->assertArrayHasKey('jwt', $data); + + $authenticated = $data['authenticated']; + $user = $data['user']; + $jwt = $data['jwt']; + + $actual = $authenticated; + $this->assertTrue($actual); + + $expected = $dbUser['usr_id']; + $actual = $user['id']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_email']; + $actual = $user['email']; + $this->assertSame($expected, $actual); + + $expected = trim( + $dbUser['usr_name_last'] + . ', ' + . $dbUser['usr_name_first'] + . ' ' + . $dbUser['usr_name_middle'] + ); + + $actual = $user['name']; + $this->assertSame($expected, $actual); + + $this->assertArrayHasKey('token', $jwt); + $this->assertArrayHasKey('refreshToken', $jwt); + + $this->assertNotEmpty($jwt['token']); + $this->assertNotEmpty($jwt['refreshToken']); + } + + public function testHandlerWrongCredentials(): void + { + /** @var AuthLoginPostHandler $handler */ + $handler = $this->container->get(AuthLoginPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + $faker = Factory::create(); + + /** + * Issue a wrong password + */ + $payload = [ + 'email' => $faker->email(), + 'password' => $faker->password(), + ]; + $command = $factory->authenticate($payload); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppIncorrectCredentials->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } + + public function testHandlerWrongCredentialsForUser(): void + { + /** @var AuthLoginPostHandler $handler */ + $handler = $this->container->get(AuthLoginPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + $migration = new UsersMigration($this->getConnection()); + + $password = 'password'; + + /** + * Issue a wrong password + */ + $dbUser = $this->getNewUser($migration, ['usr_password' => $password]); + $email = $dbUser['usr_email']; + $payload = [ + 'email' => $email, + 'password' => $password . '2', + ]; + + $command = $factory->authenticate($payload); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppIncorrectCredentials->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Application/Auth/Handler/LogoutPostHandlerTest.php b/tests/Unit/Domain/Application/Auth/Handler/LogoutPostHandlerTest.php new file mode 100644 index 0000000..8dddcab --- /dev/null +++ b/tests/Unit/Domain/Application/Auth/Handler/LogoutPostHandlerTest.php @@ -0,0 +1,140 @@ + + * + * 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\Application\Auth\Handler; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactory; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthLoginPostHandler; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthLogoutPostHandler; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\Cache\Cache; + +final class LogoutPostHandlerTest extends AbstractUnitTestCase +{ + public function testHandlerEmptyToken(): void + { + /** @var AuthLogoutPostHandler $handler */ + $handler = $this->container->get(AuthLogoutPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $command = $factory->authenticate([]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppTokenNotPresent->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } + + public function testHandlerSuccess(): void + { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var AuthLoginPostHandler $handler */ + $loginHandler = $this->container->get(AuthLoginPostHandler::class); + /** @var Cache $cache */ + $cache = $this->container->getShared(Cache::class); + /** @var AuthLogoutPostHandler $handler */ + $handler = $this->container->get(AuthLogoutPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + $migration = new UsersMigration($this->getConnection()); + + /** + * Setting the password to something we know + */ + $password = 'password'; + + $dbUser = $this->getNewUser($migration, ['usr_password' => $password]); + $email = $dbUser['usr_email']; + $input = [ + 'email' => $email, + 'password' => $password, + ]; + + $command = $factory->authenticate($input); + $payload = $loginHandler->__invoke($command); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $this->assertArrayHasKey('authenticated', $data); + + $authenticated = $data['authenticated']; + + $actual = $authenticated; + $this->assertTrue($actual); + + $token = $data['jwt']['token']; + $domainUser = $userMapper->domain($dbUser); + $tokenKey = CacheConstants::getCacheTokenKey($domainUser, $token); + + $actual = $cache->has($tokenKey); + $this->assertTrue($actual); + + $refreshToken = $data['jwt']['refreshToken']; + $tokenKey = CacheConstants::getCacheTokenKey($domainUser, $refreshToken); + + $actual = $cache->has($tokenKey); + $this->assertTrue($actual); + + $refreshToken = $data['jwt']['refreshToken']; + + /** + * Logout now + */ + $input = [ + 'token' => $refreshToken, + ]; + $command = $factory->logout($input); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $this->assertArrayHasKey('authenticated', $data); + + $authenticated = $data['authenticated']; + + $actual = $authenticated; + $this->assertFalse($actual); + + $actual = $cache->has($tokenKey); + $this->assertFalse($actual); + } +} diff --git a/tests/Unit/Domain/Application/Auth/Handler/RefreshPostHandlerTest.php b/tests/Unit/Domain/Application/Auth/Handler/RefreshPostHandlerTest.php new file mode 100644 index 0000000..bfbf86e --- /dev/null +++ b/tests/Unit/Domain/Application/Auth/Handler/RefreshPostHandlerTest.php @@ -0,0 +1,320 @@ + + * + * 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\Application\Auth\Handler; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Application\Auth\Command\AuthCommandFactory; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthLoginPostHandler; +use Phalcon\Api\Domain\Application\Auth\Handler\AuthRefreshPostHandler; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\Encryption\Security\JWT\Token\Item; +use Phalcon\Encryption\Security\JWT\Token\Token; + +final class RefreshPostHandlerTest extends AbstractUnitTestCase +{ + public function testHandlerEmptyToken(): void + { + /** @var AuthRefreshPostHandler $handler */ + $handler = $this->container->get(AuthRefreshPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $command = $factory->refresh([]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppTokenNotPresent->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } + + public function testHandlerInvalidToken(): void + { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + $user = $this->getNewUserData(); + $user['usr_id'] = 1; + $domainUser = $userMapper->domain($user); + $errors = [ + ['Incorrect token data'], + ]; + + /** + * Set up mock handlers + */ + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() + ; + $mockItem->method('get')->willReturn(true); + + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() + ; + $mockToken->method('getClaims')->willReturn($mockItem); + + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + 'getUser', + 'validate', + ] + ) + ->getMock() + ; + $mockJWT->method('getObject')->willReturn($mockToken); + $mockJWT->method('getUser')->willReturn($domainUser); + $mockJWT->method('validate')->willReturn($errors); + + + /** + * Replace the handler with the mocked one + */ + $this->container->set(JWTToken::class, $mockJWT); + + /** @var AuthRefreshPostHandler $handler */ + $handler = $this->container->get(AuthRefreshPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $command = $factory->refresh(['token' => '1234']); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = ['Incorrect token data']; + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } + + public function testHandlerNotRefreshToken(): void + { + /** + * Set up mock handlers + */ + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() + ; + $mockItem->method('get')->willReturn(false); + + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() + ; + $mockToken->method('getClaims')->willReturn($mockItem); + + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + ] + ) + ->getMock() + ; + $mockJWT->method('getObject')->willReturn($mockToken); + + /** + * Replace the handler with the mocked one + */ + $this->container->set(JWTToken::class, $mockJWT); + + /** @var AuthRefreshPostHandler $handler */ + $handler = $this->container->get(AuthRefreshPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $command = $factory->refresh(['token' => '1234']); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppTokenNotValid->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } + + public function testHandlerWithCredentials(): void + { + /** @var AuthLoginPostHandler $handler */ + $loginHandler = $this->container->get(AuthLoginPostHandler::class); + /** @var AuthRefreshPostHandler $handler */ + $handler = $this->container->get(AuthRefreshPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $migration = new UsersMigration($this->getConnection()); + + /** + * Setting the password to something we know + */ + $password = 'password'; + + $dbUser = $this->getNewUser($migration, ['usr_password' => $password]); + $email = $dbUser['usr_email']; + $payload = [ + 'email' => $email, + 'password' => $password, + ]; + + $command = $factory->authenticate($payload); + $payload = $loginHandler->__invoke($command); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $result = $payload->getResult(); + $jwt = $result['data']['jwt']; + + $command = $factory->refresh(['token' => $jwt['refreshToken']]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $this->assertArrayHasKey('token', $data); + $this->assertArrayHasKey('refreshToken', $data); + + $this->assertIsString($data['token']); + $this->assertIsString($data['refreshToken']); + $this->assertNotEmpty($data['token']); + $this->assertNotEmpty($data['refreshToken']); + } + + public function testHandlerWrongUser(): void + { + /** + * Set up mock handlers + */ + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() + ; + $mockItem->method('get')->willReturn(true); + + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() + ; + $mockToken->method('getClaims')->willReturn($mockItem); + + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + 'getUser', + ] + ) + ->getMock() + ; + $mockJWT->method('getObject')->willReturn($mockToken); + $mockJWT->method('getUser')->willReturn(null); + + + /** + * Replace the handler with the mocked one + */ + $this->container->set(JWTToken::class, $mockJWT); + + /** @var AuthRefreshPostHandler $handler */ + $handler = $this->container->get(AuthRefreshPostHandler::class); + /** @var AuthCommandFactory $factory */ + $factory = $this->container->get(AuthCommandFactory::class); + + $command = $factory->refresh(['token' => '1234']); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppTokenInvalidUser->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } +} From c0cca08183580b4aedf8e80f031d62e18d46478f Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:14:59 -0600 Subject: [PATCH 18/23] [#.x] - added user handlers --- .../User/Handler/UserDeleteHandler.php | 64 +++ .../User/Handler/UserGetHandler.php | 60 +++ .../User/Handler/UserPostHandler.php | 119 ++++ .../User/Handler/UserPutHandler.php | 129 +++++ .../User/Handler/UserHandlerDeleteTest.php | 99 ++++ .../User/Handler/UserHandlerGetTest.php | 165 ++++++ .../User/Handler/UserHandlerPostTest.php | 408 ++++++++++++++ .../User/Handler/UserHandlerPutTest.php | 510 ++++++++++++++++++ 8 files changed, 1554 insertions(+) create mode 100644 src/Domain/Application/User/Handler/UserDeleteHandler.php create mode 100644 src/Domain/Application/User/Handler/UserGetHandler.php create mode 100644 src/Domain/Application/User/Handler/UserPostHandler.php create mode 100644 src/Domain/Application/User/Handler/UserPutHandler.php create mode 100644 tests/Unit/Domain/Application/User/Handler/UserHandlerDeleteTest.php create mode 100644 tests/Unit/Domain/Application/User/Handler/UserHandlerGetTest.php create mode 100644 tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php create mode 100644 tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php diff --git a/src/Domain/Application/User/Handler/UserDeleteHandler.php b/src/Domain/Application/User/Handler/UserDeleteHandler.php new file mode 100644 index 0000000..bb830fe --- /dev/null +++ b/src/Domain/Application/User/Handler/UserDeleteHandler.php @@ -0,0 +1,64 @@ + + * + * 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\Application\User\Handler; + +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Application\User\Command\UserDeleteCommand; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\CommandBus\HandlerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; + +final readonly class UserDeleteHandler implements HandlerInterface +{ + /** + * @param UserRepositoryInterface $repository + */ + public function __construct( + private UserRepositoryInterface $repository, + ) { + } + + /** + * Get a user. + * + * @param CommandInterface $command + * + * @return Payload + */ + public function __invoke(CommandInterface $command): Payload + { + /** @var UserDeleteCommand $command */ + $userId = $command->id; + + /** + * Success + */ + if ($userId > 0) { + $rowCount = $this->repository->deleteById($userId); + + if (0 !== $rowCount) { + return Payload::deleted( + [ + 'Record deleted successfully [#' . $userId . '].', + ], + ); + } + } + + /** + * 404 + */ + return Payload::notFound(); + } +} diff --git a/src/Domain/Application/User/Handler/UserGetHandler.php b/src/Domain/Application/User/Handler/UserGetHandler.php new file mode 100644 index 0000000..4638e99 --- /dev/null +++ b/src/Domain/Application/User/Handler/UserGetHandler.php @@ -0,0 +1,60 @@ + + * + * 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\Application\User\Handler; + +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Application\User\Command\UserGetCommand; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\CommandBus\HandlerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; + +final class UserGetHandler implements HandlerInterface +{ + /** + * @param UserRepositoryInterface $repository + */ + public function __construct( + private readonly UserRepositoryInterface $repository, + ) { + } + + /** + * Get a user. + * + * @param CommandInterface $command + * + * @return Payload + */ + public function __invoke(CommandInterface $command): Payload + { + /** @var UserGetCommand $command */ + $userId = $command->id; + + /** + * Success + */ + if ($userId > 0) { + $user = $this->repository->findById($userId); + + if (null !== $user) { + return Payload::success([$user->id => $user->toArray()]); + } + } + + /** + * 404 + */ + return Payload::notFound(); + } +} diff --git a/src/Domain/Application/User/Handler/UserPostHandler.php b/src/Domain/Application/User/Handler/UserPostHandler.php new file mode 100644 index 0000000..a0e42b6 --- /dev/null +++ b/src/Domain/Application/User/Handler/UserPostHandler.php @@ -0,0 +1,119 @@ + + * + * 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\Application\User\Handler; + +use PDOException; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; + +/** + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + * @phpstan-import-type TUserDbRecordOptional from UserTypes + */ +final class UserPostHandler extends AbstractUserPutPostHandler +{ + /** + * Create a user. + * + * @param CommandInterface $command + * + * @return Payload + */ + public function __invoke(CommandInterface $command): Payload + { + $validation = $this->validator->validate($command); + if (!$validation->isValid()) { + return Payload::invalid($validation->getErrors()); + } + + /** + * Array for inserting + */ + $user = $this->mapper->db($command); + + /** + * Pre-insert checks and manipulations + */ + $user = $this->preInsert($user); + + /** + * Insert the record + */ + try { + $userId = $this->repository->insert($user); + } catch (PDOException $ex) { + /** + * @todo send generic response and log the error + */ + return $this->getErrorPayload( + HttpCodesEnum::AppCannotCreateDatabaseRecord, + $ex->getMessage() + ); + } + + if ($userId < 1) { + return $this->getErrorPayload( + HttpCodesEnum::AppCannotCreateDatabaseRecord, + 'No id returned' + ); + } + + /** + * Get the user from the database + */ + /** @var User $domainUser */ + $domainUser = $this->repository->findById($userId); + + /** + * Return the user back + */ + return Payload::created([$domainUser->id => $domainUser->toArray()]); + } + + /** + * @param TUserDomainToDbRecord $input + * + * @return TUserDbRecordOptional + */ + private function preInsert(array $input): array + { + $result = $this->processPassword($input); + $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + /** @var User $sessionUser */ + $sessionUser = $this->registry->get('user'); + + /** + * Created and updated user IDs come from the user in the session + */ + $result['usr_created_usr_id'] = $sessionUser->id; + $result['usr_updated_usr_id'] = $sessionUser->id; + + /** + * Set the created/updated dates if need be + */ + if (true === empty($result['usr_created_date'])) { + $result['usr_created_date'] = $now; + } + if (true === empty($result['usr_updated_date'])) { + $result['usr_updated_date'] = $now; + } + + /** @var TUserDbRecordOptional $result */ + return $this->cleanupFields($result); + } +} diff --git a/src/Domain/Application/User/Handler/UserPutHandler.php b/src/Domain/Application/User/Handler/UserPutHandler.php new file mode 100644 index 0000000..12ad103 --- /dev/null +++ b/src/Domain/Application/User/Handler/UserPutHandler.php @@ -0,0 +1,129 @@ + + * + * 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\Application\User\Handler; + +use PDOException; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Application\User\Command\UserPutCommand; +use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; + +/** + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + * @phpstan-import-type TUserDbRecordOptional from UserTypes + */ +final class UserPutHandler extends AbstractUserPutPostHandler +{ + /** + * Delete a user. + * + * @param CommandInterface $command + * + * @return Payload + */ + public function __invoke(CommandInterface $command): Payload + { + $validation = $this->validator->validate($command); + if (!$validation->isValid()) { + return Payload::invalid($validation->getErrors()); + } + + /** + * Check if the user exists, If not, return an error + */ + /** @var UserPutCommand $command */ + $domainUser = $this->repository->findById($command->id); + + if (null === $domainUser) { + return Payload::notFound(); + } + + /** + * Array for updating + */ + $user = $this->mapper->db($command); + + /** + * Pre-update checks and manipulations + */ + $user = $this->preUpdate($user); + + /** + * Update the record + */ + try { + $userId = $this->repository->update($command->id, $user); + } catch (PDOException $ex) { + /** + * @todo send generic response and log the error + */ + return $this->getErrorPayload( + HttpCodesEnum::AppCannotUpdateDatabaseRecord, + $ex->getMessage() + ); + } + + if ($userId < 1) { + return $this->getErrorPayload( + HttpCodesEnum::AppCannotUpdateDatabaseRecord, + 'No id returned' + ); + } + + /** + * Get the user from the database + */ + /** @var User $domainUser */ + $domainUser = $this->repository->findById($userId); + + /** + * Return the user back + */ + return Payload::updated([$domainUser->id => $domainUser->toArray()]); + } + + /** + * @param TUserDomainToDbRecord $input + * + * @return TUserDbRecordOptional + */ + private function preUpdate(array $input): array + { + $result = $this->processPassword($input); + $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + /** @var User $sessionUser */ + $sessionUser = $this->registry->get('user'); + + $result['usr_updated_usr_id'] = $sessionUser->id; + + /** + * Set updated date to now if it has not been set + */ + if (true === empty($result['usr_updated_date'])) { + $result['usr_updated_date'] = $now; + } + + /** + * Remove createdDate and createdUserId - cannot be changed. This + * needs to be here because we don't want to touch those fields. + */ + unset($result['usr_created_date'], $result['usr_created_usr_id']); + + + return $this->cleanupFields($result); + } +} diff --git a/tests/Unit/Domain/Application/User/Handler/UserHandlerDeleteTest.php b/tests/Unit/Domain/Application/User/Handler/UserHandlerDeleteTest.php new file mode 100644 index 0000000..ef51fa8 --- /dev/null +++ b/tests/Unit/Domain/Application/User/Handler/UserHandlerDeleteTest.php @@ -0,0 +1,99 @@ + + * + * 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\Application\User\Handler; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Application\User\Handler\UserDeleteHandler; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; + +final class UserHandlerDeleteTest extends AbstractUnitTestCase +{ + public function testHandlerWithUserId(): void + { + /** @var UserDeleteHandler $handler */ + $handler = $this->container->get(UserDeleteHandler::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + /** + * We need to ask for a user to be deleted with an ID that does not + * exist in the database. To ensure that, we will create a user, + * delete it and then try to delete the same user with that ID + */ + $migration = new UsersMigration($this->getConnection()); + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + + $command = $factory->delete(['id' => $userId]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::DELETED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $expected = [ + 'Record deleted successfully [#' . $userId . '].', + ]; + $actual = $data; + $this->assertSame($expected, $actual); + + /** + * Now deleting it again, will result in a 404 + */ + $payload = $handler->__invoke($command); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Record(s) not found']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerZeroUserId(): void + { + /** @var UserDeleteHandler $handler */ + $handler = $this->container->get(UserDeleteHandler::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $command = $factory->delete(['id' => 0]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Record(s) not found']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Application/User/Handler/UserHandlerGetTest.php b/tests/Unit/Domain/Application/User/Handler/UserHandlerGetTest.php new file mode 100644 index 0000000..3161502 --- /dev/null +++ b/tests/Unit/Domain/Application/User/Handler/UserHandlerGetTest.php @@ -0,0 +1,165 @@ + + * + * 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\Application\User\Handler; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Application\User\Handler\UserGetHandler; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; + +final class UserHandlerGetTest extends AbstractUnitTestCase +{ + public function testHandlerEmptyUserId(): void + { + /** @var UserGetHandler $handler */ + $handler = $this->container->get(UserGetHandler::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $command = $factory->get([]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Record(s) not found']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerWithUserId(): void + { + /** @var UserGetHandler $handler */ + $handler = $this->container->get(UserGetHandler::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $migration = new UsersMigration($this->getConnection()); + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + + $command = $factory->get(['id' => $userId]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $user = $actual['data']; + $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_email']; + $actual = $user['email']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_password']; + $actual = $user['password']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_name_prefix']; + $actual = $user['namePrefix']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_name_first']; + $actual = $user['nameFirst']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_name_middle']; + $actual = $user['nameMiddle']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_name_last']; + $actual = $user['nameLast']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_name_suffix']; + $actual = $user['nameSuffix']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_issuer']; + $actual = $user['issuer']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_token_password']; + $actual = $user['tokenPassword']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_token_id']; + $actual = $user['tokenId']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_preferences']; + $actual = $user['preferences']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_created_date']; + $actual = $user['createdDate']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_created_usr_id']; + $actual = $user['createdUserId']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_updated_date']; + $actual = $user['updatedDate']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_updated_usr_id']; + $actual = $user['updatedUserId']; + $this->assertSame($expected, $actual); + } + + public function testHandlerWrongUserId(): void + { + /** @var UserGetHandler $handler */ + $handler = $this->container->get(UserGetHandler::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $command = $factory->get(['id' => 999999]); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Record(s) not found']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php b/tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php new file mode 100644 index 0000000..cb9256e --- /dev/null +++ b/tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php @@ -0,0 +1,408 @@ + + * + * 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\Application\User\Handler; + +use DateTimeImmutable; +use PayloadInterop\DomainStatus; +use PDOException; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Application\User\Handler\UserPostHandler; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Support\Registry; + +use function htmlspecialchars; + +final class UserHandlerPostTest extends AbstractUnitTestCase +{ + public function testHandlerFailureNoIdReturned(): void + { + $userRepository = $this + ->getMockBuilder(UserRepository::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'insert', + ] + ) + ->getMock() + ; + $userRepository->method('insert')->willReturn(0); + + $this->container->setShared(UserRepository::class, $userRepository); + + /** @var UserPostHandler $handler */ + $handler = $this->container->get(UserPostHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; + + /** + * $userData is a db record. We need a domain object here + */ + $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + + $domainData = $domainUser->toArray(); + $command = $factory->insert($domainData); + + $payload = $handler->__invoke($command); + + $expected = DomainStatus::ERROR; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Cannot create database record: No id returned']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerFailurePdoError(): void + { + $userRepository = $this + ->getMockBuilder(UserRepository::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'insert', + ] + ) + ->getMock() + ; + $userRepository + ->method('insert') + ->willThrowException(new PDOException('abcde')) + ; + + $this->container->setShared(UserRepository::class, $userRepository); + + /** @var UserPostHandler $handler */ + $handler = $this->container->get(UserPostHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; + + /** + * $userData is a db record. We need a domain object here + */ + $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + + $domainData = $domainUser->toArray(); + $command = $factory->insert($domainData); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::ERROR; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Cannot create database record: abcde']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerFailureValidation(): void + { + /** @var UserPostHandler $handler */ + $handler = $this->container->get(UserPostHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $userData = $this->getNewUserData(); + + unset( + $userData['usr_email'], + $userData['usr_password'], + $userData['usr_issuer'], + $userData['usr_token_password'], + $userData['usr_token_id'] + ); + + /** + * $userData is a db record. We need a domain object here + */ + $domainUser = $userMapper->domain($userData); + $domainData = $domainUser->toArray(); + $command = $factory->insert($domainData); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::INVALID; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [ + ['Field email is required'], + ['Field email must be an email address'], + ['Field password is required'], + ['Field issuer is required'], + ['Field tokenPassword is required'], + ['Field tokenId is required'], + ]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerSuccess(): void + { + /** @var UserPostHandler $handler */ + $handler = $this->container->get(UserPostHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $userData = $this->getNewUserData(); + + /** + * $userData is a db record. We need a domain object here + */ + $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + + $domainData = $domainUser->toArray(); + $command = $factory->insert($domainData); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::CREATED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $userId = array_key_first($data); + + $this->assertGreaterThan(0, $userId); + + $data = $data[$userId]; + + $expected = $domainData['status']; + $actual = $data['status']; + $this->assertSame($expected, $actual); + + $expected = $domainData['email']; + $actual = $data['email']; + $this->assertSame($expected, $actual); + + $actual = str_starts_with($data['password'], '$argon2i$'); + $this->assertTrue($actual); + + $expected = htmlspecialchars($domainData['namePrefix']); + $actual = $data['namePrefix']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameFirst']); + $actual = $data['nameFirst']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameMiddle']); + $actual = $data['nameMiddle']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameLast']); + $actual = $data['nameLast']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameSuffix']); + $actual = $data['nameSuffix']; + $this->assertSame($expected, $actual); + + $expected = $domainData['issuer']; + $actual = $data['issuer']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenPassword']; + $actual = $data['tokenPassword']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenId']; + $actual = $data['tokenId']; + $this->assertSame($expected, $actual); + + $actual = $data['preferences']; + $this->assertNull($actual); + + $expected = $domainData['createdDate']; + $actual = $data['createdDate']; + $this->assertSame($expected, $actual); + + $expected = $domainUser->id; + $actual = $data['createdUserId']; + $this->assertSame($expected, $actual); + + $expected = $domainData['updatedDate']; + $actual = $data['updatedDate']; + $this->assertSame($expected, $actual); + + $expected = $domainUser->id; + $actual = $data['updatedUserId']; + $this->assertSame($expected, $actual); + } + + public function testHandlerSuccessEmptyDates(): void + { + $now = new DateTimeImmutable(); + $today = $now->format('Y-m-d'); + /** @var UserPostHandler $handler */ + $handler = $this->container->get(UserPostHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $userData = $this->getNewUserData(); + unset( + $userData['usr_created_date'], + $userData['usr_updated_date'], + ); + + /** + * $userData is a db record. We need a domain object here + */ + $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + + $domainData = $domainUser->toArray(); + $command = $factory->insert($domainData); + + $payload = $handler->__invoke($command); + + $expected = DomainStatus::CREATED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $userId = array_key_first($data); + + $this->assertGreaterThan(0, $userId); + + $data = $data[$userId]; + + $expected = $domainData['status']; + $actual = $data['status']; + $this->assertSame($expected, $actual); + + $expected = $domainData['email']; + $actual = $data['email']; + $this->assertSame($expected, $actual); + + $actual = str_starts_with($data['password'], '$argon2i$'); + $this->assertTrue($actual); + + $expected = htmlspecialchars($domainData['namePrefix']); + $actual = $data['namePrefix']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameFirst']); + $actual = $data['nameFirst']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameMiddle']); + $actual = $data['nameMiddle']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameLast']); + $actual = $data['nameLast']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameSuffix']); + $actual = $data['nameSuffix']; + $this->assertSame($expected, $actual); + + $expected = $domainData['issuer']; + $actual = $data['issuer']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenPassword']; + $actual = $data['tokenPassword']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenId']; + $actual = $data['tokenId']; + $this->assertSame($expected, $actual); + + $actual = $data['preferences']; + $this->assertNull($actual); + + $actual = $data['createdDate']; + $this->assertStringContainsString($today, $actual); + + $expected = 0; + $actual = $data['createdUserId']; + $this->assertSame($expected, $actual); + + $actual = $data['updatedDate']; + $this->assertStringContainsString($today, $actual); + + $expected = 0; + $actual = $data['updatedUserId']; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php b/tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php new file mode 100644 index 0000000..7be5e9b --- /dev/null +++ b/tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php @@ -0,0 +1,510 @@ + + * + * 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\Application\User\Handler; + +use DateTimeImmutable; +use PayloadInterop\DomainStatus; +use PDOException; +use Phalcon\Api\Domain\Application\User\Command\UserCommandFactory; +use Phalcon\Api\Domain\Application\User\Handler\UserPutHandler; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mapper\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\Support\Registry; + +final class UserHandlerPutTest extends AbstractUnitTestCase +{ + public function testHandlerFailureNoIdReturned(): void + { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + $userData = $this->getNewUserData(); + + $userData['usr_id'] = 1; + + $findByUser = $userMapper->domain($userData); + + $userRepository = $this + ->getMockBuilder(UserRepository::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'update', + 'findById', + ] + ) + ->getMock() + ; + $userRepository->method('update')->willReturn(0); + $userRepository->method('findById')->willReturn($findByUser); + + $this->container->setShared(UserRepository::class, $userRepository); + + /** @var UserPutHandler $handler */ + $handler = $this->container->get(UserPutHandler::class); + + /** + * Update user + */ + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; + + $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + + $updateUser = $updateUser->toArray(); + $command = $factory->update($updateUser); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::ERROR; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Cannot update database record: No id returned']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerFailurePdoError(): void + { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + $userData = $this->getNewUserData(); + + $userData['usr_id'] = 1; + + $findByUser = $userMapper->domain($userData); + + $userRepository = $this + ->getMockBuilder(UserRepository::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'update', + 'findById', + ] + ) + ->getMock() + ; + $userRepository->method('findById')->willReturn($findByUser); + $userRepository + ->method('update') + ->willThrowException(new PDOException('abcde')) + ; + + $this->container->setShared(UserRepository::class, $userRepository); + + /** @var UserPutHandler $handler */ + $handler = $this->container->get(UserPutHandler::class); + + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; + + /** + * $userData is a db record. We need a domain object here + */ + $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + + $updateUser = $updateUser->toArray(); + $command = $factory->update($updateUser); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::ERROR; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Cannot update database record: abcde']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerFailureRecordNotFound(): void + { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + $userData = $this->getNewUserData(); + + $userData['usr_id'] = 1; + + $userRepository = $this + ->getMockBuilder(UserRepository::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'findById', + ] + ) + ->getMock() + ; + $userRepository->method('findById')->willReturn(null); + + $this->container->setShared(UserRepository::class, $userRepository); + + /** @var UserPutHandler $handler */ + $handler = $this->container->get(UserPutHandler::class); + + /** + * Update user + */ + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; + + $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + + $updateUser = $updateUser->toArray(); + $command = $factory->update($updateUser); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [['Record(s) not found']]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerFailureValidation(): void + { + /** @var UserPutHandler $handler */ + $handler = $this->container->get(UserPutHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + $userData = $this->getNewUserData(); + + $userData['usr_email'] = 'wrong-email'; + unset($userData['usr_id']); + + /** + * $userData is a db record. We need a domain object here + */ + $updateUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $updateUser); + + $updateUser = $updateUser->toArray(); + $command = $factory->update($updateUser); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::INVALID; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = [ + ['Field id is not a valid absolute integer and greater than 0'], + ['Field email must be an email address'], + ]; + $actual = $errors; + $this->assertSame($expected, $actual); + } + + public function testHandlerSuccess(): void + { + /** @var UserPutHandler $handler */ + $handler = $this->container->get(UserPutHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $migration = new UsersMigration($this->getConnection()); + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + $userData = $this->getNewUserData(); + $userData['usr_id'] = $userId; + /** + * Don't hash the password + */ + $userData['usr_password'] = $this->getStrongPassword(); + + $userData['usr_created_usr_id'] = 4; + + /** + * $userData is a db record. We need a domain object here + */ + $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + + $domainData = $domainUser->toArray(); + $command = $factory->update($domainData); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UPDATED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $this->assertArrayHasKey($userId, $data); + + $data = $data[$userId]; + + $expected = $domainData['status']; + $actual = $data['status']; + $this->assertSame($expected, $actual); + + $expected = $domainData['email']; + $actual = $data['email']; + $this->assertSame($expected, $actual); + + $actual = str_starts_with($data['password'], '$argon2i$'); + $this->assertTrue($actual); + + $expected = htmlspecialchars($domainData['namePrefix']); + $actual = $data['namePrefix']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameFirst']); + $actual = $data['nameFirst']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameMiddle']); + $actual = $data['nameMiddle']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameLast']); + $actual = $data['nameLast']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameSuffix']); + $actual = $data['nameSuffix']; + $this->assertSame($expected, $actual); + + $expected = $domainData['issuer']; + $actual = $data['issuer']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenPassword']; + $actual = $data['tokenPassword']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenId']; + $actual = $data['tokenId']; + $this->assertSame($expected, $actual); + + $expected = ''; + $actual = $data['preferences']; + $this->assertSame($expected, $actual); + + /** + * These have to be the same on an update + */ + $expected = $dbUser['usr_created_date']; + $actual = $data['createdDate']; + $this->assertSame($expected, $actual); + + /** + * These have to be the same on an update + */ + $expected = 0; + $actual = $data['createdUserId']; + $this->assertSame($expected, $actual); + + $expected = $domainData['updatedDate']; + $actual = $data['updatedDate']; + $this->assertSame($expected, $actual); + + $expected = $domainUser->id; + $actual = $data['updatedUserId']; + $this->assertSame($expected, $actual); + } + + public function testHandlerSuccessEmptyDates(): void + { + $now = new DateTimeImmutable(); + $today = $now->format('Y-m-d'); + /** @var UserPutHandler $handler */ + $handler = $this->container->get(UserPutHandler::class); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(UserMapper::class); + /** @var Registry $registry */ + $registry = $this->container->get(Registry::class); + /** @var UserCommandFactory $factory */ + $factory = $this->container->get(UserCommandFactory::class); + + $migration = new UsersMigration($this->getConnection()); + $dbUser = $this->getNewUser($migration); + + $userId = $dbUser['usr_id']; + $userData = $this->getNewUserData(); + $userData['usr_id'] = $userId; + /** + * Don't hash the password + */ + $userData['usr_password'] = $this->getStrongPassword(); + + unset( + $userData['usr_updated_date'], + ); + + /** + * $userData is a db record. We need a domain object here + */ + $domainUser = $userMapper->domain($userData); + /** + * Store the domain user in the registry - to be used for *_usr_id + * field updates + */ + $registry->set('user', $domainUser); + + $domainData = $domainUser->toArray(); + $command = $factory->update($domainData); + $payload = $handler->__invoke($command); + + $expected = DomainStatus::UPDATED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('data', $actual); + + $data = $actual['data']; + + $this->assertArrayHasKey($userId, $data); + + $data = $data[$userId]; + + $expected = $domainData['status']; + $actual = $data['status']; + $this->assertSame($expected, $actual); + + $expected = $domainData['email']; + $actual = $data['email']; + $this->assertSame($expected, $actual); + + $actual = str_starts_with($data['password'], '$argon2i$'); + $this->assertTrue($actual); + + $expected = htmlspecialchars($domainData['namePrefix']); + $actual = $data['namePrefix']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameFirst']); + $actual = $data['nameFirst']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameMiddle']); + $actual = $data['nameMiddle']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameLast']); + $actual = $data['nameLast']; + $this->assertSame($expected, $actual); + + $expected = htmlspecialchars($domainData['nameSuffix']); + $actual = $data['nameSuffix']; + $this->assertSame($expected, $actual); + + $expected = $domainData['issuer']; + $actual = $data['issuer']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenPassword']; + $actual = $data['tokenPassword']; + $this->assertSame($expected, $actual); + + $expected = $domainData['tokenId']; + $actual = $data['tokenId']; + $this->assertSame($expected, $actual); + + $expected = ''; + $actual = $data['preferences']; + $this->assertSame($expected, $actual); + + /** + * These have to be the same on an update + */ + $expected = $dbUser['usr_created_date']; + $actual = $data['createdDate']; + $this->assertSame($expected, $actual); + + /** + * These have to be the same on an update + */ + $expected = 0; + $actual = $data['createdUserId']; + $this->assertSame($expected, $actual); + + $today = date('Y-m-d '); + $actual = $data['updatedDate']; + $this->assertStringContainsString($today, $actual); + + $expected = $domainUser->id; + $actual = $data['updatedUserId']; + $this->assertSame($expected, $actual); + } +} From e2343a479f4384a68a4cdc68382c077fda5d46fe Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:15:18 -0600 Subject: [PATCH 19/23] [#.x] - adjusted test --- .../Encryption/TokenManagerTest.php | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php b/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php index a35a98c..af5e024 100644 --- a/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php +++ b/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php @@ -14,31 +14,51 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Encryption; use Exception; -use Phalcon\Api\Domain\Infrastructure\Container; -use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; -use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCacheInterface; use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCache; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCacheInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Encryption\Security\JWT\Token\Token; use function rand; final class TokenManagerTest extends AbstractUnitTestCase { + public function testGetObjectSuccess(): void + { + /** @var EnvManager $env */ + $env = $this->container->get(EnvManager::class); + /** @var TokenCacheInterface $tokenCache */ + $tokenCache = $this->container->get(TokenCache::class); + /** @var JWTToken $jwtToken */ + $jwtToken = $this->container->get(JwtToken::class); + $userData = $this->getNewUserData(); + $userData['usr_id'] = rand(1, 100); + + $token = $this->getUserToken($userData); + + $manager = new TokenManager($tokenCache, $env, $jwtToken); + + $expected = Token::class; + $actual = $manager->getObject($token); + $this->assertInstanceOf($expected, $actual); + } + public function testGetObjectTokenEmpty(): void { /** @var EnvManager $env */ - $env = $this->container->get(Container::ENV); + $env = $this->container->get(EnvManager::class); /** @var TokenCacheInterface $tokenCache */ - $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + $tokenCache = $this->container->get(TokenCache::class); /** @var JWTToken $jwtToken */ - $jwtToken = $this->container->get(Container::JWT_TOKEN); + $jwtToken = $this->container->get(JwtToken::class); $manager = new TokenManager($tokenCache, $env, $jwtToken); $expected = null; - $actual = $manager->getObject(''); + $actual = $manager->getObject(''); $this->assertSame($expected, $actual); $actual = $manager->getObject(null); @@ -48,9 +68,9 @@ public function testGetObjectTokenEmpty(): void public function testGetObjectWithException(): void { /** @var EnvManager $env */ - $env = $this->container->get(Container::ENV); + $env = $this->container->get(EnvManager::class); /** @var TokenCacheInterface $tokenCache */ - $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + $tokenCache = $this->container->get(TokenCache::class); $mockJWTToken = $this ->getMockBuilder(JWTToken::class) ->disableOriginalConstructor() @@ -69,30 +89,10 @@ public function testGetObjectWithException(): void $manager = new TokenManager($tokenCache, $env, $mockJWTToken); $expected = null; - $actual = $manager->getObject(''); + $actual = $manager->getObject(''); $this->assertSame($expected, $actual); $actual = $manager->getObject(null); $this->assertSame($expected, $actual); } - - public function testGetObjectSuccess(): void - { - /** @var EnvManager $env */ - $env = $this->container->get(Container::ENV); - /** @var TokenCacheInterface $tokenCache */ - $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); - /** @var JWTToken $jwtToken */ - $jwtToken = $this->container->get(Container::JWT_TOKEN); - $userData = $this->getNewUserData(); - $userData['usr_id'] = rand(1, 100); - - $token = $this->getUserToken($userData); - - $manager = new TokenManager($tokenCache, $env, $jwtToken); - - $expected = Token::class; - $actual = $manager->getObject($token); - $this->assertInstanceOf($expected, $actual); - } } From c8f99e374f8fccab272350a29030706ab1b40703 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 20:15:36 -0600 Subject: [PATCH 20/23] [#.x] - added abstract user service --- .../User/Service/AbstractUserService.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/Domain/Application/User/Service/AbstractUserService.php diff --git a/src/Domain/Application/User/Service/AbstractUserService.php b/src/Domain/Application/User/Service/AbstractUserService.php new file mode 100644 index 0000000..32ebeb5 --- /dev/null +++ b/src/Domain/Application/User/Service/AbstractUserService.php @@ -0,0 +1,28 @@ + + * + * 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\Application\User\Service; + +use Phalcon\Api\Domain\ADR\DomainInterface; +use Phalcon\Api\Domain\Application\User\Facade\UserFacade; + +abstract class AbstractUserService implements DomainInterface +{ + /** + * @param UserFacade $facade + */ + public function __construct( + protected readonly UserFacade $facade, + ) { + } +} From 80bf78025a500165becc98ca3bdf8a9dd653c663 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 28 Nov 2025 22:05:35 -0600 Subject: [PATCH 21/23] [#.x] - added more tests --- .../CommandBus/ContainerHandlerLocatorTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.php b/tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.php index 89fd346..48c3d30 100644 --- a/tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.php +++ b/tests/Unit/Domain/Infrastructure/CommandBus/ContainerHandlerLocatorTest.php @@ -14,6 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\CommandBus; use Phalcon\Api\Domain\Infrastructure\CommandBus\ContainerHandlerLocator; +use Phalcon\Api\Domain\Infrastructure\Exceptions\HandlerRuntimeException; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Application\Command\CommandFixture; use Phalcon\Api\Tests\Fixtures\Domain\Application\Handler\HandlerFixture; @@ -40,4 +41,20 @@ public function testDispatch(): void $actual = $result; $this->assertInstanceOf($expected, $actual); } + + public function testDispatchError(): void + { + $this->expectException(HandlerRuntimeException::class); + $this->expectExceptionMessage( + 'No handler configured for ' . CommandFixture::class + ); + + $name = uniqid('name-'); + $command = new CommandFixture($name); + + /** @var ContainerHandlerLocator $locator */ + $locator = $this->container->get(ContainerHandlerLocator::class); + + $locator->resolve($command); + } } From 33dff5321be51f59f021489cd89063f5dee62221 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 29 Nov 2025 09:07:18 -0600 Subject: [PATCH 22/23] [#.x] - changed sanitizing to strip_tags and adjusted tests --- .../Enums/Sanitizer/UserSanitizerEnum.php | 2 +- .../User/Command/UserCommandFactoryTest.php | 24 +++++++++---------- .../User/Handler/UserHandlerPostTest.php | 22 ++++++++--------- .../User/Service/UserServicePostTest.php | 22 ++++++++--------- .../User/Sanitizer/UserSanitizerTest.php | 18 +++++++------- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/Domain/Infrastructure/Enums/Sanitizer/UserSanitizerEnum.php b/src/Domain/Infrastructure/Enums/Sanitizer/UserSanitizerEnum.php index ee3eea5..498e66e 100644 --- a/src/Domain/Infrastructure/Enums/Sanitizer/UserSanitizerEnum.php +++ b/src/Domain/Infrastructure/Enums/Sanitizer/UserSanitizerEnum.php @@ -57,7 +57,7 @@ public function sanitizer(): string self::password, self::tokenId, self::tokenPassword => '', // Password will be distorted - default => Filter::FILTER_STRING + default => Filter::FILTER_STRIPTAGS }; } } diff --git a/tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php b/tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php index 310519b..f1ff36e 100644 --- a/tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php +++ b/tests/Unit/Domain/Application/User/Command/UserCommandFactoryTest.php @@ -139,19 +139,19 @@ public function testInsert(): void $expected = $input['password']; $actual = $command->password; $this->assertSame($expected, $actual); - $expected = $filter->string($input['namePrefix']); + $expected = $filter->striptags($input['namePrefix']); $actual = $command->namePrefix; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameFirst']); + $expected = $filter->striptags($input['nameFirst']); $actual = $command->nameFirst; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameMiddle']); + $expected = $filter->striptags($input['nameMiddle']); $actual = $command->nameMiddle; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameLast']); + $expected = $filter->striptags($input['nameLast']); $actual = $command->nameLast; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameSuffix']); + $expected = $filter->striptags($input['nameSuffix']); $actual = $command->nameSuffix; $this->assertSame($expected, $actual); $expected = $input['issuer']; @@ -163,7 +163,7 @@ public function testInsert(): void $expected = $input['tokenId']; $actual = $command->tokenId; $this->assertSame($expected, $actual); - $expected = $filter->string($input['preferences']); + $expected = $filter->striptags($input['preferences']); $actual = $command->preferences; $this->assertSame($expected, $actual); $expected = $input['createdDate']; @@ -224,19 +224,19 @@ public function testUpdate(): void $expected = $input['password']; $actual = $command->password; $this->assertSame($expected, $actual); - $expected = $filter->string($input['namePrefix']); + $expected = $filter->striptags($input['namePrefix']); $actual = $command->namePrefix; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameFirst']); + $expected = $filter->striptags($input['nameFirst']); $actual = $command->nameFirst; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameMiddle']); + $expected = $filter->striptags($input['nameMiddle']); $actual = $command->nameMiddle; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameLast']); + $expected = $filter->striptags($input['nameLast']); $actual = $command->nameLast; $this->assertSame($expected, $actual); - $expected = $filter->string($input['nameSuffix']); + $expected = $filter->striptags($input['nameSuffix']); $actual = $command->nameSuffix; $this->assertSame($expected, $actual); $expected = $input['issuer']; @@ -248,7 +248,7 @@ public function testUpdate(): void $expected = $input['tokenId']; $actual = $command->tokenId; $this->assertSame($expected, $actual); - $expected = $filter->string($input['preferences']); + $expected = $filter->striptags($input['preferences']); $actual = $command->preferences; $this->assertSame($expected, $actual); $expected = $input['createdDate']; diff --git a/tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php b/tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php index cb9256e..0a4ac1a 100644 --- a/tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php +++ b/tests/Unit/Domain/Application/User/Handler/UserHandlerPostTest.php @@ -23,7 +23,7 @@ use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Support\Registry; -use function htmlspecialchars; +use function strip_tags; final class UserHandlerPostTest extends AbstractUnitTestCase { @@ -244,23 +244,23 @@ public function testHandlerSuccess(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -356,23 +356,23 @@ public function testHandlerSuccessEmptyDates(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php b/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php index 2bfa6bc..bef59fb 100644 --- a/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php @@ -22,7 +22,7 @@ use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Support\Registry; -use function htmlspecialchars; +use function strip_tags; final class UserServicePostTest extends AbstractUnitTestCase { @@ -234,23 +234,23 @@ public function testServiceSuccess(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -343,23 +343,23 @@ public function testServiceSuccessEmptyDates(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizerTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizerTest.php index 9e63b12..f9252cc 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizerTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizer/UserSanitizerTest.php @@ -80,18 +80,18 @@ public function testObject(): void 'status' => 4, 'email' => 'John.Doenewsletter+spam@example.COM', 'password' => 'some ', - 'namePrefix' => 'some <value>', - 'nameFirst' => 'some <value>', - 'nameLast' => 'some <value>', - 'nameMiddle' => 'some <value>', - 'nameSuffix' => 'some <value>', - 'issuer' => 'some <value>', + 'namePrefix' => 'some ', + 'nameFirst' => 'some ', + 'nameLast' => 'some ', + 'nameMiddle' => 'some ', + 'nameSuffix' => 'some ', + 'issuer' => 'some ', 'tokenPassword' => 'some ', 'tokenId' => 'some ', - 'preferences' => 'some <value>', - 'createdDate' => 'some <value>', + 'preferences' => 'some ', + 'createdDate' => 'some ', 'createdUserId' => 123, - 'updatedDate' => 'some <value>', + 'updatedDate' => 'some ', 'updatedUserId' => 123, ]; $actual = $sanitizer->sanitize($userData); From 16924b61d548711ebafbf2c36cd7601fc35d1ada Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 29 Nov 2025 15:32:12 -0600 Subject: [PATCH 23/23] [#.x] - adjusting tests; phpcs; docblocks; minor corrections --- .../CommandBus/ContainerHandlerLocator.php | 9 ++ .../Validation/ValidatorInterface.php | 6 +- .../Enums/Container/AuthDefinitionsEnum.php | 2 +- .../Enums/Container/CommonDefinitionsEnum.php | 6 +- .../Enums/Container/UserDefinitionsEnum.php | 93 +++++++++---------- .../Infrastructure/Enums/Http/RoutesEnum.php | 6 +- .../Middleware/AbstractMiddleware.php | 2 +- .../Middleware/HealthMiddleware.php | 4 +- .../ValidateTokenPresenceMiddleware.php | 4 +- .../ValidateTokenStructureMiddleware.php | 4 +- .../ValidateTokenUserMiddleware.php | 3 +- .../User/Handler/UserHandlerPutTest.php | 20 ++-- .../User/Service/UserServiceDispatchTest.php | 6 ++ .../User/Service/UserServiceGetTest.php | 2 + .../User/Service/UserServicePostTest.php | 2 + .../User/Service/UserServicePutTest.php | 24 +++-- 16 files changed, 103 insertions(+), 90 deletions(-) diff --git a/src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php b/src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php index adf46ee..2ee5363 100644 --- a/src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php +++ b/src/Domain/Infrastructure/CommandBus/ContainerHandlerLocator.php @@ -35,6 +35,15 @@ public function resolve(CommandInterface $command): HandlerInterface { $commandClass = get_class($command); + /** + * Phalcon\Api\Domain\Application\User\Command\UserGetCommand + * + * becomes + * + * Phalcon\Api\Domain\Application\User\Handler\UserGetHandler + * + * The location of files DOES matter + */ $handlerClass = str_replace( 'Command', 'Handler', diff --git a/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php b/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php index 3177cd9..59eff73 100644 --- a/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php +++ b/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php @@ -16,14 +16,14 @@ use Phalcon\Api\Domain\Infrastructure\CommandBus\CommandInterface; /** - * Validator contract. Accepts a DTO or input and returns a Result. + * Validator contract. Accepts a Command and returns a Result. */ interface ValidatorInterface { /** - * Validate a DTO or input structure. + * Validate a Command object * - * @param CommandInterface $command DTO or array + * @param CommandInterface $command * * @return Result */ diff --git a/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php index 820ccbe..2f09a74 100644 --- a/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php @@ -146,7 +146,7 @@ public function isShared(): bool } /** - * @param string $className + * @param class-string $className * * @return TService */ diff --git a/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php index c891497..70f95ea 100644 --- a/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php @@ -138,10 +138,6 @@ public function definition(): array public function isShared(): bool { - return match ($this) { - self::JWTToken, - self::JWTTokenCache => false, - default => true, - }; + return true; } } diff --git a/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php index c741396..2e19070 100644 --- a/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php @@ -106,56 +106,14 @@ public function definition(): array ], ], ], - self::UserPostHandler => [ - 'className' => UserPostHandler::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => UserValidator::class, - ], - [ - 'type' => 'service', - 'name' => UserMapper::class, - ], - [ - 'type' => 'service', - 'name' => UserRepository::class, - ], - [ - 'type' => 'service', - 'name' => Registry::class, - ], - [ - 'type' => 'service', - 'name' => Security::class, - ], - ], - ], - self::UserPutHandler => [ - 'className' => UserPutHandler::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => UserValidatorUpdate::class, - ], - [ - 'type' => 'service', - 'name' => UserMapper::class, - ], - [ - 'type' => 'service', - 'name' => UserRepository::class, - ], - [ - 'type' => 'service', - 'name' => Registry::class, - ], - [ - 'type' => 'service', - 'name' => Security::class, - ], - ], - ], + self::UserPostHandler => $this->getServicePutPost( + UserPostHandler::class, + UserValidator::class + ), + self::UserPutHandler => $this->getServicePutPost( + UserPutHandler::class, + UserValidatorUpdate::class + ), self::UserMapper => [ 'className' => UserMapper::class, ], @@ -209,6 +167,41 @@ private function getService(string $className): array ]; } + /** + * @param class-string $className + * @param class-string $validatorName + * + * @return TService + */ + private function getServicePutPost(string $className, string $validatorName): array + { + return [ + 'className' => $className, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => $validatorName, + ], + [ + 'type' => 'service', + 'name' => UserMapper::class, + ], + [ + 'type' => 'service', + 'name' => UserRepository::class, + ], + [ + 'type' => 'service', + 'name' => Registry::class, + ], + [ + 'type' => 'service', + 'name' => Security::class, + ], + ], + ]; + } + /** * @param class-string $className * diff --git a/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php index 42c805c..48660c4 100644 --- a/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php +++ b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php @@ -44,9 +44,9 @@ enum RoutesEnum: int */ public const EVENT_BEFORE = 'before'; public const EVENT_FINISH = 'finish'; - public const GET = 'get'; - public const POST = 'post'; - public const PUT = 'put'; + public const GET = 'get'; + public const POST = 'post'; + public const PUT = 'put'; case authLoginPost = 11; case authLogoutPost = 12; diff --git a/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php index 22cdba4..fafaaeb 100644 --- a/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php @@ -89,7 +89,7 @@ protected function halt( /** @var ResponseInterface $response */ $response = $application->getSharedService(Container::RESPONSE); /** @var ResponderInterface $responder */ - $responder = $application->getService(JsonResponder::class); + $responder = $application->getSharedService(JsonResponder::class); $application->stop(); diff --git a/src/Domain/Infrastructure/Middleware/HealthMiddleware.php b/src/Domain/Infrastructure/Middleware/HealthMiddleware.php index 9093fdd..ed46a0d 100644 --- a/src/Domain/Infrastructure/Middleware/HealthMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/HealthMiddleware.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Events\Exception as EventsException; -use Phalcon\Http\Request; +use Phalcon\Http\RequestInterface; use Phalcon\Http\Response\Exception; use Phalcon\Mvc\Micro; @@ -31,7 +31,7 @@ final class HealthMiddleware extends AbstractMiddleware */ public function call(Micro $application): bool { - /** @var Request $request */ + /** @var RequestInterface $request */ $request = $application->getSharedService(Container::REQUEST); if ( diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php index e4e361b..401e45e 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php @@ -17,7 +17,7 @@ use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Events\Exception as EventsException; -use Phalcon\Http\Request; +use Phalcon\Http\RequestInterface; use Phalcon\Http\Response\Exception; use Phalcon\Mvc\Micro; @@ -32,7 +32,7 @@ final class ValidateTokenPresenceMiddleware extends AbstractMiddleware */ public function call(Micro $application): bool { - /** @var Request $request */ + /** @var RequestInterface $request */ $request = $application->getSharedService(Container::REQUEST); /** @var EnvManager $env */ $env = $application->getSharedService(EnvManager::class); diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php index fdb9db6..26a3db9 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php @@ -19,7 +19,7 @@ use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Events\Exception as EventsException; -use Phalcon\Http\Request; +use Phalcon\Http\RequestInterface; use Phalcon\Http\Response\Exception; use Phalcon\Mvc\Micro; use Phalcon\Support\Registry; @@ -35,7 +35,7 @@ final class ValidateTokenStructureMiddleware extends AbstractMiddleware */ public function call(Micro $application): bool { - /** @var Request $request */ + /** @var RequestInterface $request */ $request = $application->getSharedService(Container::REQUEST); /** @var EnvManager $env */ $env = $application->getSharedService(EnvManager::class); diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php index e0899dd..7b22239 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php @@ -14,6 +14,7 @@ namespace Phalcon\Api\Domain\Infrastructure\Middleware; use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepository; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repository\UserRepositoryInterface; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; @@ -36,7 +37,7 @@ final class ValidateTokenUserMiddleware extends AbstractMiddleware */ public function call(Micro $application): bool { - /** @var UserRepository $repository */ + /** @var UserRepositoryInterface $repository */ $repository = $application->getSharedService(UserRepository::class); /** @var Registry $registry */ $registry = $application->getSharedService(Registry::class); diff --git a/tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php b/tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php index 7be5e9b..015cf82 100644 --- a/tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php +++ b/tests/Unit/Domain/Application/User/Handler/UserHandlerPutTest.php @@ -324,23 +324,23 @@ public function testHandlerSuccess(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -449,23 +449,23 @@ public function testHandlerSuccessEmptyDates(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Application/User/Service/UserServiceDispatchTest.php b/tests/Unit/Domain/Application/User/Service/UserServiceDispatchTest.php index 78798f3..6e615af 100644 --- a/tests/Unit/Domain/Application/User/Service/UserServiceDispatchTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServiceDispatchTest.php @@ -24,6 +24,12 @@ use Phalcon\Cache\Cache; use PHPUnit\Framework\Attributes\BackupGlobals; +use function json_decode; +use function ob_get_clean; +use function ob_start; +use function restore_error_handler; +use function time; + #[BackupGlobals(true)] final class UserServiceDispatchTest extends AbstractUnitTestCase { diff --git a/tests/Unit/Domain/Application/User/Service/UserServiceGetTest.php b/tests/Unit/Domain/Application/User/Service/UserServiceGetTest.php index 95a5cde..7657e24 100644 --- a/tests/Unit/Domain/Application/User/Service/UserServiceGetTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServiceGetTest.php @@ -18,6 +18,8 @@ use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use function array_key_first; + final class UserServiceGetTest extends AbstractUnitTestCase { public function testServiceEmptyUserId(): void diff --git a/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php b/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php index bef59fb..d025312 100644 --- a/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServicePostTest.php @@ -22,6 +22,8 @@ use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Support\Registry; +use function array_key_first; +use function str_starts_with; use function strip_tags; final class UserServicePostTest extends AbstractUnitTestCase diff --git a/tests/Unit/Domain/Application/User/Service/UserServicePutTest.php b/tests/Unit/Domain/Application/User/Service/UserServicePutTest.php index aea8afb..6efc574 100644 --- a/tests/Unit/Domain/Application/User/Service/UserServicePutTest.php +++ b/tests/Unit/Domain/Application/User/Service/UserServicePutTest.php @@ -23,6 +23,10 @@ use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Support\Registry; +use function date; +use function str_starts_with; +use function strip_tags; + final class UserServicePutTest extends AbstractUnitTestCase { public function testServiceFailureNoIdReturned(): void @@ -313,23 +317,23 @@ public function testServiceSuccess(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -436,23 +440,23 @@ public function testServiceSuccessEmptyDates(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = htmlspecialchars($domainData['namePrefix']); + $expected = strip_tags($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameFirst']); + $expected = strip_tags($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameMiddle']); + $expected = strip_tags($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameLast']); + $expected = strip_tags($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = htmlspecialchars($domainData['nameSuffix']); + $expected = strip_tags($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual);