diff --git a/src/App/Application.php b/src/App/Application.php index 7c3ae54..2bab23e 100644 --- a/src/App/Application.php +++ b/src/App/Application.php @@ -72,7 +72,7 @@ class Application extends Container /** * The application version */ - public const VERSION = '2.0.0-dev'; + public const VERSION = '2.0.14-dev'; /** * The event dispatcher instance diff --git a/src/Auth/Authentication/BaseAuthentication.php b/src/Auth/Authentication/BaseAuthentication.php new file mode 100644 index 0000000..9bb0543 --- /dev/null +++ b/src/Auth/Authentication/BaseAuthentication.php @@ -0,0 +1,173 @@ + + */ + protected function getLoginData(User $user): array + { + $permissions = $this->getUserPermissions($user); + $data = [ + 'user' => [ + 'id' => $user->id, + 'username' => $user->username, + 'lastname' => $user->lastname, + 'firstname' => $user->firstname, + 'email' => $user->email, + 'status' => $user->status, + ], + 'permissions' => $permissions, + ]; + + return array_merge($data, $this->getUserData($user)); + } + + /** + * Return the user entity + * @param string $username + * @param string $password + * @param bool $withPassword wether to use password to login + * @return User|null + */ + protected function getUserEntity( + string $username, + string $password, + bool $withPassword = true + ): ?User { + return $this->userRepository->with('roles.permissions') + ->findBy(['username' => $username]); + } + + /** + * Return the user additional data + * @param User $user + * @return array + */ + protected function getUserData(User $user): array + { + return []; + } + + /** + * Return the user additional attribute to be saved along with login information + * @param User $user + * @return array + */ + protected function getUserAttribute(User $user): array + { + return []; + } + + /** + * Return the permission list of the given user + * @param User|int $user + * @return string[] + */ + protected function getUserPermissions(User|int $user): array + { + $permissions = []; + if (is_int($user)) { + $user = $this->userRepository->with('roles.permissions') + ->find($user); + if ($user === null) { + return []; + } + } + + $roles = $this->getUserRoles($user); + foreach ($roles as $role) { + $rolePermissions = $role->permissions; + foreach ($rolePermissions as $permission) { + if (in_array($permission->code, $permissions) === false) { + $permissions[] = $permission->code; + } + } + } + + return $permissions; + } + + /** + * Return the role of the given user + * @param User $user + * @return Role[] + */ + protected function getUserRoles(User $user): array + { + return $user->roles; + } +} diff --git a/src/Auth/Authentication/JWTAuthentication.php b/src/Auth/Authentication/JWTAuthentication.php index 5491080..cc704a7 100644 --- a/src/Auth/Authentication/JWTAuthentication.php +++ b/src/Auth/Authentication/JWTAuthentication.php @@ -50,11 +50,12 @@ use DateTime; use Platine\Config\Config; use Platine\Container\ContainerInterface; -use Platine\Framework\Auth\AuthenticationInterface; +use Platine\Framework\App\Application; use Platine\Framework\Auth\Authorization\PermissionCacheInterface; use Platine\Framework\Auth\Entity\Token; use Platine\Framework\Auth\Entity\User; use Platine\Framework\Auth\Enum\UserStatus; +use Platine\Framework\Auth\Event\AuthLoginEvent; use Platine\Framework\Auth\Exception\AccountLockedException; use Platine\Framework\Auth\Exception\AccountNotFoundException; use Platine\Framework\Auth\Exception\InvalidCredentialsException; @@ -75,29 +76,32 @@ * @package Platine\Framework\Auth\Authentication * @template T */ -class JWTAuthentication implements AuthenticationInterface +class JWTAuthentication extends BaseAuthentication { /** * Create new instance + * @param HashInterface $hash + * @param UserRepository $userRepository + * @param Application $app * @param JWT $jwt * @param LoggerInterface $logger * @param Config $config - * @param HashInterface $hash - * @param UserRepository $userRepository * @param TokenRepository $tokenRepository * @param ContainerInterface $container * @param PermissionCacheInterface $permissionCache */ public function __construct( + HashInterface $hash, + UserRepository $userRepository, + Application $app, protected JWT $jwt, protected LoggerInterface $logger, protected Config $config, - protected HashInterface $hash, - protected UserRepository $userRepository, protected TokenRepository $tokenRepository, protected ContainerInterface $container, protected PermissionCacheInterface $permissionCache ) { + parent::__construct($hash, $userRepository, $app); } /** @@ -105,12 +109,7 @@ public function __construct( */ public function getUser(): IdentityInterface { - if ($this->isLogged() === false) { - throw new AccountNotFoundException('User not logged', 401); - } - - $payload = $this->jwt->getPayload(); - $id = (int) ($payload['sub'] ?? -1); + $id = $this->getId(); $user = $this->userRepository->find($id); if ($user === null) { @@ -132,8 +131,7 @@ public function getPermissions(): array return []; } - $payload = $this->jwt->getPayload(); - $roles = $payload['roles'] ?? []; + $roles = $this->getJwtPayload('roles', []); $permissions = []; foreach ($roles as $id) { // TODO: add configuration for prefix @@ -153,7 +151,8 @@ public function getPermissions(): array // May be cache feature not available or expired // get user permissions from database - return $this->getUserPermissions((int) ($payload['sub'] ?? -1)); + $id = (int) $this->getJwtPayload('sub', -1); + return $this->getUserPermissions($id); } @@ -166,8 +165,7 @@ public function getId(): int|string throw new AccountNotFoundException('User not logged', 401); } - $payload = $this->jwt->getPayload(); - $id = (int) ($payload['sub'] ?? -1); + $id = $this->getAuthAttribute('sub'); return $id; } @@ -190,8 +188,6 @@ public function isLogged(): bool $headerName = $this->config->get('api.auth.headers.name', 'Authorization'); $tokenHeader = $request->getHeaderLine($headerName); if (empty($tokenHeader)) { - $this->logger->error('API authentication failed missing token header'); - return false; } $tokenType = $this->config->get('api.auth.headers.token_type', 'Bearer'); @@ -218,7 +214,7 @@ public function isLogged(): bool */ public function login( array $credentials = [], - bool $remeberMe = false, + bool $rememberMe = false, bool $withPassword = true ): array { if (isset($credentials['username']) === false) { @@ -261,101 +257,93 @@ public function login( ); } - $permissions = $this->getUserPermissions($user); - $roles = Arr::getColumn($user->roles, 'id'); - $secret = $this->config->get('api.sign.secret'); - $expire = $this->config->get('api.auth.token_expire', 900); - $tokenExpire = time() + $expire; - $this->jwt->setSecret($secret) - ->setPayload([ - 'sub' => $user->id, - 'exp' => $tokenExpire, - 'roles' => $roles, - ]) - ->sign(); + // Generate the JWT token + $this->generateJwtToken($user); - $jwtToken = $this->jwt->getToken(); - [$refreshToken, $token] = $this->generateRefreshToken($user->id); - - $data = [ - 'user' => [ - 'id' => $user->id, - 'username' => $user->username, - 'lastname' => $user->lastname, - 'firstname' => $user->firstname, - 'email' => $user->email, - 'status' => $user->status, - ], - 'permissions' => $permissions, - 'token' => $jwtToken, - 'refresh_token' => $refreshToken, - ]; - - return array_merge($data, $this->getUserData($user, $token)); + // Inform the system that the user just login successfully + $this->app->dispatch(new AuthLoginEvent($user)); + + return $this->getLoginData($user); } /** * {@inheritdoc} */ - public function logout(bool $destroy = true): void + public function relogin(IdentityInterface $identity): array + { + $id = $identity->getId(); + $user = $this->userRepository->find($id); + + if ($user === null) { + throw new AccountNotFoundException( + 'Can not find the logged user information, may be data is corrupted', + 401 + ); + } + + // Generate the JWT token + $this->generateJwtToken($user); + + $loginData = $this->getLoginData($user); + + return $loginData; + } + + /** + * {@inheritdoc} + */ + public function getAuthAttribute(string $key, mixed $default = null): mixed { - // do nothing now + return $this->getJwtPayload($key, $default); } /** - * Return the user entity - * @param string $username - * @param string $password - * @param bool $withPassword wether to use password to login - * @return User|null + * {@inheritdoc} */ - protected function getUserEntity( - string $username, - string $password, - bool $withPassword = true - ): ?User { - return $this->userRepository->with('roles.permissions') - ->findBy(['username' => $username]); + public function logout(bool $destroy = true): void + { + if ($this->isLogged() === false) { + return; + } + $userId = $this->getId(); + $this->tokenRepository->query() + ->where('user_id')->is($userId) + ->delete(); } /** - * Return the user additional data + * Generate the JWT token * @param User $user - * @param Token $token - * @return array + * @return void */ - protected function getUserData(User $user, Token $token): array + protected function generateJwtToken(User $user): void { - return []; + $roles = $this->getUserRoles($user); + $roleIds = Arr::getColumn($roles, 'id'); + $secret = $this->config->get('api.sign.secret'); + $expire = $this->config->get('api.auth.token_expire', 900); + $tokenExpire = time() + $expire; + $payload = [ + 'sub' => $user->id, + 'exp' => $tokenExpire, + 'roles' => $roleIds, + ] + $this->getUserAttribute($user); + $this->jwt->setSecret($secret) + ->setPayload($payload) + ->sign(); } /** - * Return the permission list of the given user - * @param User|int $user - * @return string[] + * {@inheritdoc} */ - protected function getUserPermissions(User|int $user): array + protected function getLoginData(User $user): array { - $permissions = []; - if (is_int($user)) { - $user = $this->userRepository->with('roles.permissions') - ->find($user); - if ($user === null) { - return []; - } - } - - $roles = $user->roles; - foreach ($roles as $role) { - $rolePermissions = $role->permissions; - foreach ($rolePermissions as $permission) { - if (in_array($permission->code, $permissions) === false) { - $permissions[] = $permission->code; - } - } - } + $loginData = parent::getLoginData($user); + $jwtToken = $this->jwt->getToken(); + [$refreshToken, ] = $this->generateRefreshToken($user->id); - return $permissions; + return $loginData + ['token' => $jwtToken, + 'refresh_token' => $refreshToken,]; } /** @@ -387,4 +375,16 @@ protected function generateRefreshToken(int $userId): array return [$refreshToken, $token]; } + + /** + * Return the JWT payload value + * @param string $key + * @param mixed $default + * @return mixed + */ + protected function getJwtPayload(string $key, mixed $default = null): mixed + { + $payload = $this->jwt->getPayload(); + return $payload[$key] ?? $default; + } } diff --git a/src/Auth/Authentication/SessionAuthentication.php b/src/Auth/Authentication/SessionAuthentication.php index 856be9f..7613135 100644 --- a/src/Auth/Authentication/SessionAuthentication.php +++ b/src/Auth/Authentication/SessionAuthentication.php @@ -48,8 +48,6 @@ namespace Platine\Framework\Auth\Authentication; use Platine\Framework\App\Application; -use Platine\Framework\Auth\AuthenticationInterface; -use Platine\Framework\Auth\Entity\User; use Platine\Framework\Auth\Enum\UserStatus; use Platine\Framework\Auth\Event\AuthInvalidPasswordEvent; use Platine\Framework\Auth\Event\AuthLoginEvent; @@ -66,21 +64,22 @@ * class SessionAuthentication * @package Platine\Framework\Auth\Authentication */ -class SessionAuthentication implements AuthenticationInterface +class SessionAuthentication extends BaseAuthentication { /** * Create new instance - * @param Application $app * @param HashInterface $hash - * @param Session $session * @param UserRepository $userRepository + * @param Application $app + * @param Session $session */ public function __construct( - protected Application $app, - protected HashInterface $hash, + HashInterface $hash, + UserRepository $userRepository, + Application $app, protected Session $session, - protected UserRepository $userRepository ) { + parent::__construct($hash, $userRepository, $app); } /** @@ -88,11 +87,7 @@ public function __construct( */ public function getUser(): IdentityInterface { - if ($this->isLogged() === false) { - throw new AccountNotFoundException('User not logged', 401); - } - - $id = $this->session->get('auth.user.id'); + $id = $this->getId(); $user = $this->userRepository->find($id); if ($user === null) { @@ -110,7 +105,7 @@ public function getUser(): IdentityInterface */ public function getPermissions(): array { - return $this->session->get('auth.permissions', []); + return $this->getAuthAttribute('permissions', []); } @@ -123,9 +118,7 @@ public function getId(): int|string throw new AccountNotFoundException('User not logged', 401); } - $id = $this->session->get('user.id'); - - return $id; + return $this->session->get('auth.user.id'); } /** @@ -141,7 +134,7 @@ public function isLogged(): bool */ public function login( array $credentials = [], - bool $remeberMe = false, + bool $rememberMe = false, bool $withPassword = true ): array { if (!isset($credentials['username'])) { @@ -186,28 +179,7 @@ public function login( ); } - $permissions = []; - $roles = $user->roles; - foreach ($roles as $role) { - $rolePermissions = $role->permissions; - foreach ($rolePermissions as $permission) { - $permissions[] = $permission->code; - } - } - - $data = [ - 'user' => [ - 'id' => $user->id, - 'username' => $user->username, - 'lastname' => $user->lastname, - 'firstname' => $user->firstname, - 'email' => $user->email, - 'status' => $user->status, - ], - 'permissions' => $permissions, - ]; - - $loginData = array_merge($data, $this->getUserData($user)); + $loginData = $this->getLoginData($user); $this->session->set('auth', $loginData); @@ -217,6 +189,28 @@ public function login( return $loginData; } + /** + * {@inheritdoc} + */ + public function relogin(IdentityInterface $identity): array + { + $id = $identity->getId(); + $user = $this->userRepository->find($id); + + if ($user === null) { + throw new AccountNotFoundException( + 'Can not find the logged user information, may be data is corrupted', + 401 + ); + } + + $loginData = $this->getLoginData($user); + + $this->session->set('auth', $loginData); + + return $loginData; + } + /** * {@inheritdoc} */ @@ -241,28 +235,10 @@ public function logout(bool $destroy = true): void } /** - * Return the user entity - * @param string $username - * @param string $password - * @param bool $withPassword wether to use password to login - * @return User|null - */ - protected function getUserEntity( - string $username, - string $password, - bool $withPassword = true - ): ?User { - return $this->userRepository->with('roles.permissions') - ->findBy(['username' => $username]); - } - - /** - * Return the user additional data - * @param User $user - * @return array + * {@inheritdoc} */ - protected function getUserData(User $user): array + public function getAuthAttribute(string $key, mixed $default = null): mixed { - return []; + return $this->session->get(sprintf('auth.%s', $key), $default); } } diff --git a/src/Auth/AuthenticationInterface.php b/src/Auth/AuthenticationInterface.php index 733d3af..8a39b94 100644 --- a/src/Auth/AuthenticationInterface.php +++ b/src/Auth/AuthenticationInterface.php @@ -61,7 +61,7 @@ interface AuthenticationInterface /** * Authenticate the user * @param array $credentials - * @param bool $remeberMe + * @param bool $rememberMe * @param bool $withPassword wether to use password to login * @return array * @@ -72,10 +72,17 @@ interface AuthenticationInterface */ public function login( array $credentials = [], - bool $remeberMe = false, + bool $rememberMe = false, bool $withPassword = true ): array; + /** + * Re-authenticate the user + * @param IdentityInterface $identity + * @return array + */ + public function relogin(IdentityInterface $identity): array; + /** * Check if user is logged * @return bool @@ -111,4 +118,12 @@ public function getId(): int|string; * @return string[] */ public function getPermissions(): array; + + /** + * Return the user authentication data + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getAuthAttribute(string $key, mixed $default = null): mixed; } diff --git a/src/Config/DatabaseConfigLoader.php b/src/Config/DatabaseConfigLoader.php index 5498186..4bf0237 100644 --- a/src/Config/DatabaseConfigLoader.php +++ b/src/Config/DatabaseConfigLoader.php @@ -49,6 +49,7 @@ use Platine\Database\Query\WhereStatement; use Platine\Framework\Config\Model\Configuration; +use Platine\Framework\Enum\YesNoStatus; use Platine\Orm\Entity; /** @@ -109,9 +110,9 @@ public function all(): array { return $this->repository->all(); } - + /** - * Return the configuration + * Return the all configurations * @param string $group * @param string|null $env * @param array $filters the filters to use if any @@ -126,10 +127,10 @@ protected function loadDbConfigurations( $query = $this->repository->filters($filters) ->query() ->where('module')->is($group) - ->where('status')->is('Y') + ->where('status')->is(YesNoStatus::YES) ->where(function (WhereStatement $where) use ($env) { $where->where('env')->is($env) - ->orWhere('env')->isNull(); + ->orWhere('env')->isNull(); }); // @codeCoverageIgnoreEnd diff --git a/src/Console/Command/InfoCommand.php b/src/Console/Command/InfoCommand.php new file mode 100644 index 0000000..9c2fc61 --- /dev/null +++ b/src/Console/Command/InfoCommand.php @@ -0,0 +1,136 @@ + $config + * @param Application $application + */ + public function __construct( + protected Config $config, + protected Application $application + ) { + parent::__construct('info', 'Command to show deployment information'); + } + + /** + * {@inheritdoc} + */ + public function execute(): mixed + { + $writer = $this->io()->writer(); + $writer->boldGreen('Deployment information', true)->eol(); + + $writer->bold('OS: '); + $writer->boldBlueBgBlack(PHP_OS, true); + + $writer->bold('Architecture: '); + $writer->boldBlueBgBlack(php_uname('m'), true); + + $writer->bold('PHP Version: '); + $writer->boldBlueBgBlack(PHP_VERSION, true); + + $writer->bold('Platine Version: '); + $writer->boldBlueBgBlack($this->application->version(), true); + + $writer->bold('Platine Environment: '); + $writer->boldBlueBgBlack($this->application->getEnvironment(), true); + + $writer->bold('Debug mode: '); + $writer->boldBlueBgBlack($this->config->get('app.debug', false) ? 'On' : 'Off', true); + + $packages = $this->getPackages(); + + $writer->bold('Installed Packages: '); + $writer->boldBlueBgBlack((string) count($packages), true); + + /** @var array>> $rows*/ + $rows = []; + foreach ($packages as $package) { + $rows[] = [ + 'name' => $package['name'], + 'description' => Str::limit($package['description'], 60), + 'version' => $package['version'], + 'type' => $package['type'], + 'dev' => $package['dev'] ? 'Yes' : 'No', + ]; + } + $writer->table($rows); + + $writer->green('Command finished successfully')->eol(); + + return true; + } + + /** + * Return the composer packages + * @return array> + */ + protected function getPackages(): array + { + $packages = Composer::parseLockFile($this->application->getRootPath()); + Arr::multisort($packages, 'dev'); + + return $packages; + } +} diff --git a/src/Console/Command/MakeActionCommand.php b/src/Console/Command/MakeActionCommand.php index 343b4a1..674aee8 100644 --- a/src/Console/Command/MakeActionCommand.php +++ b/src/Console/Command/MakeActionCommand.php @@ -53,6 +53,7 @@ use Platine\Framework\App\Application; use Platine\Framework\Console\MakeCommand; use Platine\Framework\Http\Action\BaseAction; +use Platine\Stdlib\Helper\Php; use Platine\Stdlib\Helper\Str; /** @@ -124,6 +125,7 @@ public function interact(Reader $reader, Writer $writer): void public function getClassTemplate(): string { $baseAction = $this->getOptionValue('baseAction'); + $baseClassName = Php::getShortClassName($baseAction); return <<> + */ + protected array $cacheKeys = []; + + /** * Create new instance * @param Application $application @@ -118,6 +125,14 @@ public function __construct(protected AppDatabaseConfig \$dbConfig) */ protected function getConfigMethod(Entity $entity, ?string $module = null): string { + if (isset($this->cacheKeys[$entity->module][$entity->name])) { + return ''; + } + + if (isset($this->cacheKeys[$entity->module]) === false) { + $this->cacheKeys[$entity->module][$entity->name] = true; + } + $types = $this->getDataTypeMaps(); $methodTemplate = $this->getMethodTemplate(); diff --git a/src/Env/Env.php b/src/Env/Env.php index 05ef0d5..955d64a 100644 --- a/src/Env/Env.php +++ b/src/Env/Env.php @@ -55,9 +55,10 @@ class Env { /** - * The custom filter validate used for array values + * The custom filter validate used for array, duration values */ protected const FILTER_VALIDATE_ARRAY = 111900; + protected const FILTER_VALIDATE_DURATION = 111901; /** * Get the environment variable by its key/name. @@ -82,6 +83,7 @@ public static function get( 'ip' => FILTER_VALIDATE_IP, 'url' => FILTER_VALIDATE_URL, 'array' => self::FILTER_VALIDATE_ARRAY, // special case + 'duration' => self::FILTER_VALIDATE_DURATION, // special case ]; if (isset($filterMaps[$filter])) { @@ -145,6 +147,10 @@ protected static function prepareValue( return explode($separator, $value); } + if ($filter === self::FILTER_VALIDATE_DURATION) { + return static::getDurationValue($value); + } + if ($filter === null || function_exists('filter_var') === false) { return $valueResolved; } @@ -152,6 +158,34 @@ protected static function prepareValue( return filter_var($valueResolved, $filter, $options); } + /** + * Return the duration value + * @param string $value + * @return int + */ + protected static function getDurationValue(string $value): int + { + $maps = [ + 'ms' => fn($val) => (int)($val / 1000), + 's' => fn($val) => $val, + 'm' => fn($val) => $val * 60, + 'h' => fn($val) => $val * 3600, + 'd' => fn($val) => $val * 86400, + 'w' => fn($val) => $val * 604800, + ]; + + $val = strtolower($value); + foreach ($maps as $unit => $cb) { + if (strpos($val, $unit) !== false) { + $val = str_replace($unit, '', $val); + return $cb((int) $val); + } + } + + // default + return (int) $value; + } + /** * Resolve variable reference like ${VAR_NAME} * @param string $value diff --git a/src/Helper/ActionHelper.php b/src/Helper/ActionHelper.php index 3502394..0d6a7af 100644 --- a/src/Helper/ActionHelper.php +++ b/src/Helper/ActionHelper.php @@ -46,56 +46,47 @@ * @package Platine\Framework\Helper * @template T */ -class ActionHelper +class ActionHelper extends BaseActionHelper { /** * Create new instance * @param Pagination $pagination - * @param Sidebar $sidebar * @param ViewContext $context - * @param Template $template * @param RouteHelper $routeHelper - * @param Flash $flash * @param Lang $lang * @param LoggerInterface $logger * @param Auditor $auditor * @param FileHelper $fileHelper * @param Config $config + * @param Sidebar $sidebar + * @param Template $template + * @param Flash $flash */ public function __construct( - protected Pagination $pagination, + Pagination $pagination, + ViewContext $context, + RouteHelper $routeHelper, + Lang $lang, + LoggerInterface $logger, + Auditor $auditor, + FileHelper $fileHelper, + Config $config, protected Sidebar $sidebar, - protected ViewContext $context, protected Template $template, - protected RouteHelper $routeHelper, protected Flash $flash, - protected Lang $lang, - protected LoggerInterface $logger, - protected Auditor $auditor, - protected FileHelper $fileHelper, - protected Config $config ) { + parent::__construct( + $pagination, + $context, + $routeHelper, + $lang, + $logger, + $auditor, + $fileHelper, + $config + ); } - /** - * Return the configuration instance - * @return Config - */ - public function getConfig(): Config - { - return $this->config; - } - - /** - * Return the FileHelper - * @return FileHelper - */ - public function getFileHelper(): FileHelper - { - return $this->fileHelper; - } - - /** * * @return Flash @@ -105,42 +96,6 @@ public function getFlash(): Flash return $this->flash; } - /** - * - * @return Lang - */ - public function getLang(): Lang - { - return $this->lang; - } - - /** - * - * @return LoggerInterface - */ - public function getLogger(): LoggerInterface - { - return $this->logger; - } - - /** - * - * @return Auditor - */ - public function getAuditor(): Auditor - { - return $this->auditor; - } - - /** - * - * @return Pagination - */ - public function getPagination(): Pagination - { - return $this->pagination; - } - /** * * @return Sidebar @@ -150,15 +105,6 @@ public function getSidebar(): Sidebar return $this->sidebar; } - /** - * - * @return ViewContext - */ - public function getContext(): ViewContext - { - return $this->context; - } - /** * * @return Template @@ -167,13 +113,4 @@ public function getTemplate(): Template { return $this->template; } - - /** - * - * @return RouteHelper - */ - public function getRouteHelper(): RouteHelper - { - return $this->routeHelper; - } } diff --git a/src/Helper/BaseActionHelper.php b/src/Helper/BaseActionHelper.php new file mode 100644 index 0000000..8fd1fc0 --- /dev/null +++ b/src/Helper/BaseActionHelper.php @@ -0,0 +1,144 @@ + $context + * @param RouteHelper $routeHelper + * @param Lang $lang + * @param LoggerInterface $logger + * @param Auditor $auditor + * @param FileHelper $fileHelper + * @param Config $config + */ + public function __construct( + protected Pagination $pagination, + protected ViewContext $context, + protected RouteHelper $routeHelper, + protected Lang $lang, + protected LoggerInterface $logger, + protected Auditor $auditor, + protected FileHelper $fileHelper, + protected Config $config + ) { + } + + /** + * Return the configuration instance + * @return Config + */ + public function getConfig(): Config + { + return $this->config; + } + + /** + * Return the FileHelper + * @return FileHelper + */ + public function getFileHelper(): FileHelper + { + return $this->fileHelper; + } + + /** + * + * @return Lang + */ + public function getLang(): Lang + { + return $this->lang; + } + + /** + * + * @return LoggerInterface + */ + public function getLogger(): LoggerInterface + { + return $this->logger; + } + + /** + * + * @return Auditor + */ + public function getAuditor(): Auditor + { + return $this->auditor; + } + + /** + * + * @return Pagination + */ + public function getPagination(): Pagination + { + return $this->pagination; + } + + /** + * + * @return ViewContext + */ + public function getContext(): ViewContext + { + return $this->context; + } + + /** + * + * @return RouteHelper + */ + public function getRouteHelper(): RouteHelper + { + return $this->routeHelper; + } +} diff --git a/src/Helper/EntityHelper.php b/src/Helper/EntityHelper.php index 6e6e277..0a70cb6 100644 --- a/src/Helper/EntityHelper.php +++ b/src/Helper/EntityHelper.php @@ -109,11 +109,9 @@ public function subscribeEvents( if ($this->ignore) { return; } - if ($this->authentication->isLogged() === false) { - return; - } $auditor = $this->auditor; + $authentication = $this->authentication; $fieldIgnores = [ ...$ignoreFields, ...['password', 'created_at', 'updated_at'], @@ -125,8 +123,12 @@ public function subscribeEvents( DataMapper $dm ) use ( $auditor, + $authentication, $fieldIgnores ) { + if ($authentication->isLogged() === false) { + return; + } $data = $entity->jsonSerialize(); $entityData = Arr::except($data, $fieldIgnores); $className = Php::getShortClassName($entity); @@ -147,8 +149,12 @@ public function subscribeEvents( DataMapper $dm ) use ( $auditor, + $authentication, $fieldIgnores ) { + if ($authentication->isLogged() === false) { + return; + } $data = $entity->jsonSerialize(); $entityData = Arr::except($data, $fieldIgnores); $className = Php::getShortClassName($entity); @@ -169,8 +175,12 @@ public function subscribeEvents( DataMapper $dm ) use ( $auditor, + $authentication, $fieldIgnores ) { + if ($authentication->isLogged() === false) { + return; + } $data = $entity->jsonSerialize(); $entityData = Arr::except($data, $fieldIgnores); $className = Php::getShortClassName($entity); diff --git a/src/Http/Action/BaseAction.php b/src/Http/Action/BaseAction.php index 6f33892..ccc34b9 100644 --- a/src/Http/Action/BaseAction.php +++ b/src/Http/Action/BaseAction.php @@ -33,29 +33,14 @@ namespace Platine\Framework\Http\Action; -use Platine\Config\Config; -use Platine\Framework\Audit\Auditor; use Platine\Framework\Helper\ActionHelper; -use Platine\Framework\Helper\FileHelper; use Platine\Framework\Helper\Flash; use Platine\Framework\Helper\Sidebar; -use Platine\Framework\Helper\ViewContext; -use Platine\Framework\Http\RequestData; use Platine\Framework\Http\Response\RedirectResponse; -use Platine\Framework\Http\Response\RestResponse; use Platine\Framework\Http\Response\TemplateResponse; -use Platine\Framework\Http\RouteHelper; use Platine\Framework\Security\SecurityPolicy; -use Platine\Http\Handler\RequestHandlerInterface; use Platine\Http\ResponseInterface; -use Platine\Http\ServerRequestInterface; -use Platine\Lang\Lang; -use Platine\Logger\LoggerInterface; -use Platine\Orm\Query\EntityQuery; -use Platine\Orm\RepositoryInterface; -use Platine\Pagination\Pagination; use Platine\Stdlib\Helper\Arr; -use Platine\Stdlib\Helper\Str; use Platine\Template\Template; /** @@ -63,139 +48,32 @@ * @package Platine\Framework\Http\Action * @template T */ -abstract class BaseAction implements RequestHandlerInterface +abstract class BaseAction extends BaseHttpAction { - /** - * The field to use in query - * @var string[] - */ - protected array $fields = []; - - /** - * The field columns maps - * @var array - */ - protected array $fieldMaps = []; - - /** - * The filter list - * @var array - */ - protected array $filters = []; - - /** - * The filters name maps - * @var array - */ - protected array $filterMaps = []; - - /** - * The sort information's - * @var array - */ - protected array $sorts = []; - - /** - * The pagination limit - * @var int|null - */ - protected ?int $limit = null; - - /** - * The pagination current page - * @var int|null - */ - protected ?int $page = null; - - /** - * Whether to query all list without pagination - * @var bool - */ - protected bool $all = false; - /** * The name of the view * @var string */ protected string $viewName = ''; - /** - * The pagination instance - * @var Pagination - */ - protected Pagination $pagination; - - /** - * The request to use - * @var ServerRequestInterface - */ - protected ServerRequestInterface $request; - - /** - * The request data instance - * @var RequestData - */ - protected RequestData $param; - /** * The Sidebar instance * @var Sidebar */ protected Sidebar $sidebar; - /** - * The view context - * @var ViewContext - */ - protected ViewContext $context; - /** * The template instance * @var Template */ protected Template $template; - /** - * The RouteHelper instance - * @var RouteHelper - */ - protected RouteHelper $routeHelper; - /** * The Flash instance * @var Flash */ protected Flash $flash; - /** - * The Lang instance - * @var Lang - */ - protected Lang $lang; - - /** - * The LoggerInterface instance - * @var LoggerInterface - */ - protected LoggerInterface $logger; - - /** - * The auditor instance - * @var Auditor - */ - protected Auditor $auditor; - - /** - * The file helper instance - * @var FileHelper - */ - protected FileHelper $fileHelper; - - /** - * The application configuration instance - * @var Config - */ - protected Config $config; /** * Create new instance @@ -203,33 +81,10 @@ abstract class BaseAction implements RequestHandlerInterface */ public function __construct(ActionHelper $actionHelper) { - $this->pagination = $actionHelper->getPagination(); + parent::__construct($actionHelper); $this->sidebar = $actionHelper->getSidebar(); - $this->context = $actionHelper->getContext(); $this->template = $actionHelper->getTemplate(); - $this->routeHelper = $actionHelper->getRouteHelper(); $this->flash = $actionHelper->getFlash(); - $this->lang = $actionHelper->getLang(); - $this->logger = $actionHelper->getLogger(); - $this->auditor = $actionHelper->getAuditor(); - $this->fileHelper = $actionHelper->getFileHelper(); - $this->config = $actionHelper->getConfig(); - } - - /** - * {@inheritodc} - */ - public function handle(ServerRequestInterface $request): ResponseInterface - { - $this->request = $request; - $this->param = new RequestData($request); - - $this->setFields(); - $this->setFilters(); - $this->setSorts(); - $this->setPagination(); - - return $this->respond(); } /** @@ -264,33 +119,6 @@ public function addSidebar( return $this; } - /** - * Add view context - * @param string $name - * @param mixed $value - * @return self - */ - public function addContext(string $name, mixed $value): self - { - $this->context->set($name, $value); - - return $this; - } - - /** - * Add context in one call - * @param array $data - * @return self - */ - public function addContexts(array $data): self - { - foreach ($data as $name => $value) { - $this->context->set($name, $value); - } - - return $this; - } - /** * Return the template response * @return TemplateResponse @@ -361,198 +189,6 @@ protected function redirect( return new RedirectResponse($routeUrl); } - /** - * Return the response - * @return ResponseInterface - */ - abstract public function respond(): ResponseInterface; - - /** - * Set field information's - * @return void - */ - protected function setFields(): void - { - $fieldParams = $this->param->get('fields', ''); - if (!empty($fieldParams)) { - $fields = explode(',', $fieldParams); - $columns = []; - foreach ($fields as $field) { - $columns[] = $this->fieldMaps[$field] ?? $field; - } - $this->fields = $columns; - } - } - - /** - * Set filters information's - * @return void - */ - protected function setFilters(): void - { - $queries = $this->param->gets(); - //remove defaults - unset( - $queries['fields'], - $queries['sort'], - $queries['page'], - $queries['limit'], - $queries['all'] - ); - - $this->filters = Arr::filterValue($queries); - - // Handle default filters - $this->handleFilterDefault(); - - // Handle dates filter's - if (array_key_exists('start_date', $this->filters)) { - $startDate = $this->filters['start_date']; - // if no time is provided xxxx-xx-xx - if (Str::length($startDate) === 10) { - $startDate .= ' 00:00:00'; - } - $this->filters['start_date'] = $startDate; - } - - if (array_key_exists('end_date', $this->filters)) { - $endDate = $this->filters['end_date']; - // if no time is provided xxxx-xx-xx - if (Str::length($endDate) === 10) { - $endDate .= ' 23:59:59'; - } - $this->filters['end_date'] = $endDate; - } - - $ignoreDateFilters = $this->getIgnoreDateFilters(); - - foreach ($ignoreDateFilters as $filterName) { - if (array_key_exists($filterName, $this->filters)) { - unset( - $this->filters['start_date'], - $this->filters['end_date'] - ); - break; - } - } - } - - /** - * Set sort information's - * @return void - */ - protected function setSorts(): void - { - $sortParams = $this->param->get('sort', ''); - if (!empty($sortParams)) { - $sorts = explode(',', $sortParams); - $columns = []; - foreach ($sorts as $sort) { - $order = 'ASC'; - $parts = explode(':', $sort); - if (isset($parts[1]) && strtolower($parts[1]) === 'desc') { - $order = 'DESC'; - } - - $column = $this->fieldMaps[$parts[0]] ?? $parts[0]; - $columns[$column] = $order; - } - $this->sorts = $columns; - } - } - - /** - * Set the pagination information - * @return void - */ - protected function setPagination(): void - { - $param = $this->param; - - if ($param->get('all', null)) { - $this->all = true; - return; - } - - $limit = $param->get('limit', null); - if ($limit !== null) { - $this->limit = (int) $limit; - } - - if ($this->limit !== null && $this->limit > 100) { - $this->limit = 100; - } - - $page = $param->get('page', null); - if ($page) { - $this->page = (int) $page; - } - - if ($limit > 0 || $page > 0) { - $this->all = false; - } - - if ($this->limit > 0) { - $this->pagination->setItemsPerPage($this->limit); - } - - $currentPage = $this->page ?? 1; - - $this->pagination->setCurrentPage($currentPage); - } - - /** - * Parse the error message to handle delete or update of parent record - * @param string $error - * @return string - */ - protected function parseForeignConstraintErrorMessage(string $error): string - { - /** MySQL ** - * SQLSTATE[23000]: Integrity constraint violation: 1217 Cannot delete or update a - * parent row: a foreign key constraint fails [DELETE FROM `TABLE_NAME` WHERE `id` = XX] - */ - - /** MariaDB * - * SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a - * parent row: a foreign key constraint fails - * ("DB_NAME"."TABLE_NAME", CONSTRAINT "basetable_fk_person_id" FOREIGN KEY ("person_id") - * REFERENCES "persons" ("id") ON DELETE NO ACTION) [DELETE FROM `persons` WHERE `id` = XX] - */ - $result = ''; - if (strpos($error, 'Cannot delete or update a parent row') !== false) { - if (strpos($error, 'Integrity constraint violation: 1217') !== false) { - // MySQL - $result = $this->lang->tr('This record is related to another one'); - } else { - // MariaDB - $arr = explode('.', $error); - $tmp = explode(',', $arr[1] ?? ''); - $result = $this->lang->tr('This record is related to another one [%s]', str_replace('_', ' ', $tmp[0])); - } - } - - return $result; - } - - - /** - * Handle filter default dates - * @return void - */ - protected function handleFilterDefault(): void - { - } - - /** - * Ignore date filters if one of the given filters is present - * @return array $filters - */ - protected function getIgnoreDateFilters(): array - { - return []; - } - /** * Redirect back to origin if user want to create new entity from * detail page @@ -574,243 +210,4 @@ protected function redirectBackToOrigin(): ?ResponseInterface return $this->redirect($originRoute, ['id' => $originId]); } - - /** - * Process pagination and sort - * @param RepositoryInterface $repository - * @param EntityQuery $query - * @param string|array $sortFields - * @param string $sortDir - * @return void - */ - protected function handleRestPagination( - RepositoryInterface $repository, - EntityQuery $query, - string|array $sortFields = 'name', - string $sortDir = 'ASC' - ): void { - if ($this->all === false) { - $totalItems = $repository->filters($this->filters) - ->query() - ->count('id'); - - $currentPage = (int) $this->param->get('page', 1); - - $this->pagination->setTotalItems($totalItems) - ->setCurrentPage($currentPage); - - $limit = $this->pagination->getItemsPerPage(); - $offset = $this->pagination->getOffset(); - - $query = $query->limit($limit) - ->offset($offset); - } - - if (count($this->sorts) > 0) { - foreach ($this->sorts as $column => $order) { - $query = $query->orderBy($column, $order); - } - } else { - $query = $query->orderBy($sortFields, $sortDir); - } - } - - // REST API Part - /** - * Return the rest response - * @param mixed $data - * @param int $statusCode - * @param string|int $code the custom code - * - * @return ResponseInterface - */ - protected function restResponse( - mixed $data = [], - int $statusCode = 200, - string|int $code = 'OK' - ): ResponseInterface { - $extras = $this->context->all(); - if ($this->pagination->getTotalItems() > 0) { - $extras['pagination'] = $this->pagination->getInfo(); - } - - return new RestResponse( - $data, - $extras, - true, - $code, - '', - $statusCode - ); - } - - /** - * Return the rest created response - * @param array|object|mixed $data - * @param string|int $code the custom code - * - * @return ResponseInterface - */ - protected function restCreatedResponse(mixed $data = [], string|int $code = 'CREATED'): ResponseInterface - { - return $this->restResponse( - $data, - 201, - $code - ); - } - - /** - * Return the rest no content response - * @param string|int $code the custom code - * - * @return ResponseInterface - */ - protected function restNoContentResponse(string|int $code = 'NO_CONTENT'): ResponseInterface - { - return $this->restResponse( - [], - 204, - $code - ); - } - - /** - * Return the rest error response - * @param string $message - * @param int $statusCode - * @param string|int $code - * @param array $extras - * - * @return ResponseInterface - */ - protected function restErrorResponse( - string $message, - int $statusCode = 401, - string|int $code = 'ERROR', - array $extras = [] - ): ResponseInterface { - return new RestResponse( - [], - $extras, - false, - $code, - $message, - $statusCode - ); - } - - /** - * Return the rest server error response - * @param string $message - * @param string|int $code the custom error code - * - * @return ResponseInterface - */ - protected function restServerErrorResponse( - string $message = '', - string|int $code = 'INTERNAL_SERVER_ERROR' - ): ResponseInterface { - return $this->restErrorResponse( - $message, - 500, - $code, - [] - ); - } - - /** - * Return the rest bad request error response - * @param string $message - * @param string|int $code the custom error code - * - * @return ResponseInterface - */ - protected function restBadRequestErrorResponse( - string $message = '', - string|int $code = 'BAD_REQUEST' - ): ResponseInterface { - return $this->restErrorResponse( - $message, - 400, - $code, - [] - ); - } - - /** - * Return the rest duplicate resource error response - * @param string $message - * @param string|int $code the custom error code - * - * @return ResponseInterface - */ - protected function restConflictErrorResponse( - string $message = '', - string|int $code = 'DUPLICATE_RESOURCE' - ): ResponseInterface { - return $this->restErrorResponse( - $message, - 409, - $code, - [] - ); - } - - /** - * Return the rest not found error response - * @param string $message - * @param string|int $code the custom error code - * - * @return ResponseInterface - */ - protected function restNotFoundErrorResponse( - string $message = '', - string|int $code = 'RESOURCE_NOT_FOUND' - ): ResponseInterface { - return $this->restErrorResponse( - $message, - 404, - $code, - [] - ); - } - - /** - * Return the rest unauthorized error response - * @param string $message - * @param string|int $code the custom error code - * - * @return ResponseInterface - */ - protected function restUnauthorizedErrorResponse( - string $message = '', - string|int $code = 'UNAUTHORIZED_ACCESS' - ): ResponseInterface { - return $this->restErrorResponse( - $message, - 401, - $code, - [] - ); - } - - /** - * Return the rest form validation error response - * @param array $errors - * @param string|int $code the custom error code - * - * @return ResponseInterface - */ - protected function restFormValidationErrorResponse( - array $errors = [], - string|int $code = 'INVALID_INPUT' - ): ResponseInterface { - return $this->restErrorResponse( - $this->lang->tr('Invalid Request Parameter(s)'), - 422, - $code, - ['errors' => $errors] - ); - } } diff --git a/src/Http/Action/BaseHttpAction.php b/src/Http/Action/BaseHttpAction.php new file mode 100644 index 0000000..34a17fb --- /dev/null +++ b/src/Http/Action/BaseHttpAction.php @@ -0,0 +1,344 @@ + + */ + protected array $filters = []; + + /** + * The filters name maps + * @var array + */ + protected array $filterMaps = []; + + /** + * The pagination limit + * @var int + */ + protected int $limit; + + /** + * The pagination max limit to fetch "all" record + * @var int + */ + protected int $maxLimit = 0; + + /** + * The pagination max per page + * @var int + */ + protected int $maxPerPage = 0; + + /** + * The pagination current page + * @var int + */ + protected int $page = 1; + + /** + * The pagination instance + * @var Pagination + */ + protected Pagination $pagination; + + /** + * The request to use + * @var ServerRequestInterface + */ + protected ServerRequestInterface $request; + + /** + * The request data instance + * @var RequestData + */ + protected RequestData $param; + + /** + * The view context + * @var ViewContext + */ + protected ViewContext $context; + + /** + * The RouteHelper instance + * @var RouteHelper + */ + protected RouteHelper $routeHelper; + + /** + * The Lang instance + * @var Lang + */ + protected Lang $lang; + + /** + * The LoggerInterface instance + * @var LoggerInterface + */ + protected LoggerInterface $logger; + + /** + * The auditor instance + * @var Auditor + */ + protected Auditor $auditor; + + /** + * The file helper instance + * @var FileHelper + */ + protected FileHelper $fileHelper; + + /** + * The application configuration instance + * @var Config + */ + protected Config $config; + + /** + * Create new instance + * @param BaseActionHelper $actionHelper + */ + public function __construct(BaseActionHelper $actionHelper) + { + $this->pagination = $actionHelper->getPagination(); + $this->context = $actionHelper->getContext(); + $this->routeHelper = $actionHelper->getRouteHelper(); + $this->lang = $actionHelper->getLang(); + $this->logger = $actionHelper->getLogger(); + $this->auditor = $actionHelper->getAuditor(); + $this->fileHelper = $actionHelper->getFileHelper(); + $this->config = $actionHelper->getConfig(); + } + + /** + * {@inheritodc} + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $this->request = $request; + $this->param = new RequestData($request); + + $this->setFilters(); + $this->setPagination(); + + return $this->respond(); + } + + /** + * Add view context + * @param string $name + * @param mixed $value + * @return self + */ + public function addContext(string $name, mixed $value): self + { + $this->context->set($name, $value); + + return $this; + } + + /** + * Add context in one call + * @param array $data + * @return self + */ + public function addContexts(array $data): self + { + foreach ($data as $name => $value) { + $this->context->set($name, $value); + } + + return $this; + } + + /** + * Return the response + * @return ResponseInterface + */ + abstract public function respond(): ResponseInterface; + + /** + * Set filters information's + * @return void + */ + protected function setFilters(): void + { + $queries = $this->param->gets(); + //remove defaults + unset( + $queries['fields'], + $queries['sort'], + $queries['page'], + $queries['limit'], + $queries['all'] + ); + + $this->filters = Arr::filterValue($queries); + + // Handle default filters + $this->handleFilterDefault(); + + // Handle dates filter's + if (array_key_exists('start_date', $this->filters)) { + $startDate = $this->filters['start_date']; + // if no time is provided xxxx-xx-xx + if (Str::length($startDate) === 10) { + $startDate .= ' 00:00:00'; + } + $this->filters['start_date'] = $startDate; + } + + if (array_key_exists('end_date', $this->filters)) { + $endDate = $this->filters['end_date']; + // if no time is provided xxxx-xx-xx + if (Str::length($endDate) === 10) { + $endDate .= ' 23:59:59'; + } + $this->filters['end_date'] = $endDate; + } + + $ignoreDateFilters = $this->getIgnoreDateFilters(); + + foreach ($ignoreDateFilters as $filterName) { + if (array_key_exists($filterName, $this->filters)) { + unset( + $this->filters['start_date'], + $this->filters['end_date'] + ); + break; + } + } + } + + /** + * Set the pagination information + * @return void + */ + protected function setPagination(): void + { + $param = $this->param; + $all = boolval($param->get('all', null)); + $perPage = $this->config->get('pagination.item_per_page', 20); + + $this->limit = (int) $param->get('limit', $perPage); + $page = $param->get('page', 1); + if ($page) { + $this->page = (int) $page; + } + + if ($this->maxLimit === 0) { + $this->maxLimit = $this->config->get('pagination.max_limit', 1000); + } + + if ($this->maxPerPage === 0) { + $this->maxPerPage = $this->config->get('pagination.max_per_page', 100); + } + + // Handle fetching all records + if ($all) { + $this->page = 1; + $this->limit = $this->maxLimit; + } else { + if ($this->limit > $this->maxPerPage) { + $this->limit = $this->maxPerPage; + } + } + + $this->pagination->setItemsPerPage($this->limit); + $this->pagination->setCurrentPage($this->page); + } + + /** + * Log request exception + * @param Throwable $ex + * @return void + */ + protected function logRequestException(Throwable $ex): void + { + $this->logger->error( + 'An error occurred during the processing of the request: {error}, {file}:{line}, reference: {reference}', + [ + 'error' => $ex->getMessage(), + 'file' => $ex->getFile(), + 'line' => $ex->getLine(), + 'reference' => $this->request->getHeaderLine('X-Request-ID'), + ] + ); + } + + /** + * Handle filter default dates + * @return void + */ + protected function handleFilterDefault(): void + { + } + + /** + * Ignore date filters if one of the given filters is present + * @return array $filters + */ + protected function getIgnoreDateFilters(): array + { + return []; + } +} diff --git a/src/Http/Action/RestBaseAction.php b/src/Http/Action/RestBaseAction.php new file mode 100644 index 0000000..24034fb --- /dev/null +++ b/src/Http/Action/RestBaseAction.php @@ -0,0 +1,390 @@ + + */ + protected array $fieldMaps = []; + + /** + * The sort information's + * @var array + */ + protected array $sorts = []; + + /** + * Create new instance + * @param BaseActionHelper $actionHelper + */ + public function __construct(BaseActionHelper $actionHelper) + { + parent::__construct($actionHelper); + } + + /** + * {@inheritodc} + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $this->request = $request; + $this->param = new RequestData($request); + + $this->setFields(); + $this->setFilters(); + $this->setSorts(); + $this->setPagination(); + + return $this->respond(); + } + + /** + * Set field information's + * @return void + */ + protected function setFields(): void + { + $fieldParams = $this->param->get('fields', ''); + if (!empty($fieldParams)) { + $fields = explode(',', $fieldParams); + $columns = []; + foreach ($fields as $field) { + $columns[] = $this->fieldMaps[$field] ?? $field; + } + $this->fields = $columns; + } + } + + /** + * Set sort information's + * @return void + */ + protected function setSorts(): void + { + $sortParams = $this->param->get('sort', ''); + if (!empty($sortParams)) { + $sorts = explode(',', $sortParams); + $columns = []; + foreach ($sorts as $sort) { + $order = 'ASC'; + $parts = explode(':', $sort); + if (isset($parts[1]) && strtolower($parts[1]) === 'desc') { + $order = 'DESC'; + } + + $column = $this->fieldMaps[$parts[0]] ?? $parts[0]; + $columns[$column] = $order; + } + $this->sorts = $columns; + } + } + + /** + * Process pagination and sort + * @param RepositoryInterface $repository + * @param EntityQuery $query + * @param string|array $sortFields + * @param string $sortDir + * @return void + */ + protected function handleRestPagination( + RepositoryInterface $repository, + EntityQuery $query, + string|array $sortFields = 'name', + string $sortDir = 'ASC' + ): void { + $totalItems = $repository->filters($this->filters) + ->query() + ->count('id'); + + $this->pagination->setTotalItems($totalItems); + + $limit = $this->pagination->getItemsPerPage(); + $offset = $this->pagination->getOffset(); + + $query->limit($limit) + ->offset($offset); + + if (count($this->sorts) > 0) { + foreach ($this->sorts as $column => $order) { + $query = $query->orderBy($column, $order); + } + } else { + $query = $query->orderBy($sortFields, $sortDir); + } + } + + /** + * Return the REST response + * @param mixed $data + * @param int $statusCode + * @param string|int $code the custom code + * + * @return ResponseInterface + */ + protected function restResponse( + mixed $data = [], + int $statusCode = 200, + string|int $code = 'OK' + ): ResponseInterface { + $extras = $this->context->all(); + if ($this->pagination->getTotalItems() > 0) { + $extras['pagination'] = $this->pagination->getInfo(); + } + + return new RestResponse( + $data, + $extras, + true, + $code, + '', + $statusCode + ); + } + + /** + * Return the REST error response + * @param string $message + * @param int $statusCode + * @param string|int $code + * @param array $extras + * + * @return ResponseInterface + */ + protected function errorResponse( + string $message, + int $statusCode = 401, + string|int $code = 'ERROR', + array $extras = [] + ): ResponseInterface { + return new RestResponse( + [], + $extras, + false, + $code, + $message, + $statusCode + ); + } + + /** + * Return the REST created response + * @param array|object|mixed $data + * @param string|int $code the custom code + * + * @return ResponseInterface + */ + protected function createdResponse( + mixed $data = [], + string|int $code = 'CREATED' + ): ResponseInterface { + return $this->restResponse( + $data, + 201, + $code + ); + } + + /** + * Return the REST no content response + * @param string|int $code the custom code + * + * @return ResponseInterface + */ + protected function noContentResponse( + string|int $code = 'NO_CONTENT' + ): ResponseInterface { + return $this->restResponse( + [], + 204, + $code + ); + } + + /** + * Return the REST bad request response + * @param string $message + * @param string|int $code the custom error code + * + * @return ResponseInterface + */ + protected function badRequestResponse( + string $message = '', + string|int $code = 'BAD_REQUEST' + ): ResponseInterface { + return $this->errorResponse( + $message, + 400, + $code, + [] + ); + } + + /** + * Return the REST unauthorized response + * @param string $message + * @param string|int $code the custom error code + * + * @return ResponseInterface + */ + protected function unauthorizedResponse( + string $message = '', + string|int $code = 'UNAUTHORIZED_ACCESS' + ): ResponseInterface { + return $this->errorResponse( + $message, + 401, + $code, + [] + ); + } + + /** + * Return the REST forbidden response + * @param string $message + * @param string|int $code the custom error code + * + * @return ResponseInterface + */ + protected function forbiddenResponse( + string $message = '', + string|int $code = 'FORBIDDEN' + ): ResponseInterface { + return $this->errorResponse( + $message, + 403, + $code, + [] + ); + } + + /** + * Return the REST not found response + * @param string $message + * @param string|int $code the custom error code + * + * @return ResponseInterface + */ + protected function notFoundResponse( + string $message = '', + string|int $code = 'RESOURCE_NOT_FOUND' + ): ResponseInterface { + return $this->errorResponse( + $message, + 404, + $code, + [] + ); + } + + + /** + * Return the REST duplicate resource response + * @param string $message + * @param string|int $code the custom error code + * + * @return ResponseInterface + */ + protected function conflictResponse( + string $message = '', + string|int $code = 'DUPLICATE_RESOURCE' + ): ResponseInterface { + return $this->errorResponse( + $message, + 409, + $code, + [] + ); + } + + /** + * Return the REST form validation error response + * @param array $errors + * @param string|int $code the custom error code + * + * @return ResponseInterface + */ + protected function formValidationErrorResponse( + array $errors = [], + string|int $code = 'INVALID_INPUT' + ): ResponseInterface { + return $this->errorResponse( + $this->lang->tr('Invalid Request Parameter(s)'), + 422, + $code, + ['errors' => $errors] + ); + } + + /** + * Return the REST internal server error response + * @param string $message + * @param string|int $code the custom error code + * + * @return ResponseInterface + */ + protected function internalServerErrorResponse( + string $message = '', + string|int $code = 'INTERNAL_SERVER_ERROR' + ): ResponseInterface { + return $this->errorResponse( + $message, + 500, + $code, + [] + ); + } +} diff --git a/src/Http/Middleware/CsrfTokenMiddleware.php b/src/Http/Middleware/CsrfTokenMiddleware.php index d39ac94..9e261fb 100644 --- a/src/Http/Middleware/CsrfTokenMiddleware.php +++ b/src/Http/Middleware/CsrfTokenMiddleware.php @@ -38,7 +38,6 @@ use Platine\Http\Handler\RequestHandlerInterface; use Platine\Http\ResponseInterface; use Platine\Http\ServerRequestInterface; -use Platine\Route\Route; /** * @class CsrfTokenMiddleware @@ -62,29 +61,11 @@ public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { - if ($this->shouldBeProcessed($request) === false) { - return $handler->handle($request); - } - - $newRequest = $request->withAttribute('csrf_token', $this->csrfManager->getToken()); + $newRequest = $request->withAttribute( + 'csrf_token', + $this->csrfManager->getToken() + ); return $handler->handle($newRequest); } - - /** - * Whether we can process this request - * @param ServerRequestInterface $request - * @return bool - */ - protected function shouldBeProcessed(ServerRequestInterface $request): bool - { - //If no route has been match no need check for CSRF - /** @var Route|null $route */ - $route = $request->getAttribute(Route::class); - if ($route === null) { - return false; - } - - return true; - } } diff --git a/src/Http/Middleware/SecurityPolicyMiddleware.php b/src/Http/Middleware/SecurityPolicyMiddleware.php index 1f98d1d..38e6ed6 100644 --- a/src/Http/Middleware/SecurityPolicyMiddleware.php +++ b/src/Http/Middleware/SecurityPolicyMiddleware.php @@ -52,7 +52,6 @@ use Platine\Http\Handler\RequestHandlerInterface; use Platine\Http\ResponseInterface; use Platine\Http\ServerRequestInterface; -use Platine\Route\Route; /** * @class SecurityPolicyMiddleware @@ -76,23 +75,18 @@ public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { - - if ($this->shouldBeProcessed($request) === false) { - return $handler->handle($request); - } - // Generate the nonces to be used in script and style $scriptNonce = $this->securityPolicy->nonce('script'); $styleNonce = $this->securityPolicy->nonce('style'); - $request = $request->withAttribute(SecurityPolicy::class, [ + $newRequest = $request->withAttribute(SecurityPolicy::class, [ 'nonces' => [ 'style' => $styleNonce, 'script' => $scriptNonce, ] ]); - $response = $handler->handle($request); + $response = $handler->handle($newRequest); $headers = $this->securityPolicy->headers(); foreach ($headers as $name => $value) { @@ -101,21 +95,4 @@ public function process( return $response; } - - /** - * Whether we can process this request - * @param ServerRequestInterface $request - * @return bool - */ - protected function shouldBeProcessed(ServerRequestInterface $request): bool - { - //If no route has been match no need check for CSRF - /** @var Route|null $route */ - $route = $request->getAttribute(Route::class); - if ($route === null) { - return false; - } - - return true; - } } diff --git a/src/Security/JWT/JWT.php b/src/Security/JWT/JWT.php index 3edcaf5..7975352 100644 --- a/src/Security/JWT/JWT.php +++ b/src/Security/JWT/JWT.php @@ -273,12 +273,12 @@ public function generateSignature(): string { $this->setDefaults(); - $encodedPayload = $this->encoder->encode((string) json_encode( + $encodedPayload = $this->encoder->encode((string) Json::encode( $this->getPayload(), JSON_UNESCAPED_SLASHES )); - $encodedHeaders = $this->encoder->encode((string) json_encode( + $encodedHeaders = $this->encoder->encode((string) Json::encode( $this->getHeaders(), JSON_UNESCAPED_SLASHES )); diff --git a/src/Service/Provider/CommandServiceProvider.php b/src/Service/Provider/CommandServiceProvider.php index 4ed54ca..4b19da3 100644 --- a/src/Service/Provider/CommandServiceProvider.php +++ b/src/Service/Provider/CommandServiceProvider.php @@ -48,6 +48,7 @@ namespace Platine\Framework\Service\Provider; use Platine\Framework\Console\Command\ConfigCommand; +use Platine\Framework\Console\Command\InfoCommand; use Platine\Framework\Console\Command\MaintenanceCommand; use Platine\Framework\Console\Command\MakeActionCommand; use Platine\Framework\Console\Command\MakeCrudActionCommand; @@ -81,6 +82,7 @@ public function register(): void $this->app->bind(RouteCommand::class); $this->app->bind(MaintenanceCommand::class); $this->app->bind(ConfigCommand::class); + $this->app->bind(InfoCommand::class); $this->app->bind(VendorPublishCommand::class); $this->app->bind(MakeActionCommand::class); $this->app->bind(MakeFilterCommand::class); @@ -113,6 +115,7 @@ public function register(): void $this->addCommand(MakeListenerCommand::class); $this->addCommand(MakeTaskCommand::class); $this->addCommand(ConfigCommand::class); + $this->addCommand(InfoCommand::class); $this->addCommand(MakeResourceActionCommand::class); $this->addCommand(MakeCrudActionCommand::class); } diff --git a/src/Service/Provider/MailerSMTPServiceProvider.php b/src/Service/Provider/MailerSMTPServiceProvider.php index 2c24aec..0e88d21 100644 --- a/src/Service/Provider/MailerSMTPServiceProvider.php +++ b/src/Service/Provider/MailerSMTPServiceProvider.php @@ -60,6 +60,8 @@ public function register(): void $username = $app->get(Config::class)->get('mail.smtp.username', ''); $password = $app->get(Config::class)->get('mail.smtp.password', ''); + $encryption = $app->get(Config::class)->get('mail.smtp.encryption', SMTP::ENCRYPTION_NONE); + $smtp->setEncryption($encryption); if (!empty($username) && !empty($password)) { $smtp->setAuth($username, $password); } diff --git a/tests/App/ApplicationTest.php b/tests/App/ApplicationTest.php index 3e29e22..4c4f368 100644 --- a/tests/App/ApplicationTest.php +++ b/tests/App/ApplicationTest.php @@ -40,7 +40,7 @@ public function testConstructor(): void public function testVersion(): void { $app = new Application(''); - $this->assertEquals('2.0.0-dev', $app->version()); + $this->assertEquals('2.0.14-dev', $app->version()); } public function testGetSetAll(): void diff --git a/tests/Auth/Authentication/JWTAuthenticationTest.php b/tests/Auth/Authentication/JWTAuthenticationTest.php index 24a6de2..87ad267 100644 --- a/tests/Auth/Authentication/JWTAuthenticationTest.php +++ b/tests/Auth/Authentication/JWTAuthenticationTest.php @@ -7,7 +7,10 @@ use DateTime; use Platine\Config\Config; use Platine\Container\Container; +use Platine\Database\Query\Delete; +use Platine\Database\Query\Where; use Platine\Dev\PlatineTestCase; +use Platine\Framework\App\Application; use Platine\Framework\Auth\Authentication\JWTAuthentication; use Platine\Framework\Auth\Authorization\Cache\NullCacheStorage; use Platine\Framework\Auth\Entity\Permission; @@ -25,6 +28,7 @@ use Platine\Http\ServerRequest; use Platine\Http\ServerRequestInterface; use Platine\Logger\Logger; +use Platine\Orm\Query\EntityQuery; use Platine\Orm\Repository; use Platine\Security\Hash\BcryptHash; @@ -61,13 +65,15 @@ public function testGetUserFailed(): void $userRepository = $this->getMockInstance(UserRepository::class); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -96,13 +102,15 @@ public function testIsLoggedServerRequestNotAvailable(): void $userRepository = $this->getMockInstance(UserRepository::class); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -143,13 +151,15 @@ public function testGetUserFailedWrongJWTToken(): void ]); $userRepository = $this->getMockInstance(UserRepository::class); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -194,13 +204,15 @@ public function testGetUserNotFound(): void ] ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -245,13 +257,15 @@ public function testGetIdSuccess(): void ] ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -293,13 +307,15 @@ public function testGetIdNotLogged(): void ] ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -344,19 +360,79 @@ public function testGetUserSuccess(): void ] ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, + $tokenRepository, + $containter, + $cacheStorage + ); + + $this->assertInstanceOf(User::class, $o->getUser()); + } + + public function testLogoutSuccess(): void + { + $jwt = $this->getMockInstance(JWT::class, [ + 'getPayload' => ['sub' => 1] + ]); + $logger = $this->getMockInstance(Logger::class); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['api.auth.headers.name', 'Authorization', 'Authorization'], + ['api.auth.headers.token_type', 'Bearer', 'Bearer'], + ['api.sign.secret', '', 'foosecret'], + ] + ]); + $tokenWhereIs = $this->getMockInstance(Delete::class); + $tokenWhere = $this->getMockInstance(Where::class, [ + 'is' => $tokenWhereIs, + ]); + $tokenQuery = $this->getMockInstance(EntityQuery::class, [ + 'where' => $tokenWhere, + ]); + $tokenRepository = $this->getMockInstance(TokenRepository::class, [ + 'query' => $tokenQuery, + ]); + $hash = $this->getMockInstance(BcryptHash::class); + $request = $this->getMockInstanceMap(ServerRequest::class, [ + 'getHeaderLine' => [ + ['Authorization', '7676ghggfhfgfghg'] + ] + ]); + $containter = $this->getMockInstanceMap(Container::class, [ + 'has' => [ + [ServerRequestInterface::class, true], + ], + 'get' => [ + [ServerRequestInterface::class, $request], + ], + ]); + $userRepository = $this->getMockInstance(UserRepository::class); + $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); + + $o = new JWTAuthentication( $hash, $userRepository, + $app, + $jwt, + $logger, + $config, $tokenRepository, $containter, $cacheStorage ); - $this->assertInstanceOf(User::class, $o->getUser()); + $this->expectMethodCallCount($tokenRepository, 'query'); + + $o->logout(); } public function testGetPermissions(): void @@ -391,13 +467,15 @@ public function testGetPermissions(): void $cacheStorage = $this->getMockInstance(NullCacheStorage::class, [ 'get' => ['user_create', 'user_delete'] ]); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -438,13 +516,15 @@ public function testGetPermissionsFromDB(): void $cacheStorage = $this->getMockInstance(NullCacheStorage::class, [ 'get' => [] ]); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -483,13 +563,15 @@ public function testGetPermissionsUserNotLogged(): void ]); $userRepository = $this->getMockInstanceMap(UserRepository::class); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -524,13 +606,15 @@ public function testLoginUsernameEmpty(): void ]); $userRepository = $this->getMockInstance(UserRepository::class); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -569,13 +653,15 @@ public function testLoginPasswordEmpty(): void ]); $userRepository = $this->getMockInstance(UserRepository::class); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -614,13 +700,15 @@ public function testLogout(): void ]); $userRepository = $this->getMockInstance(UserRepository::class); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -658,13 +746,15 @@ public function testLoginUserNotFound(): void 'findBy' => null ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -712,13 +802,15 @@ public function testLoginUserIsLocked(): void 'with' => $middleRepository, ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -766,13 +858,15 @@ public function testLoginWrongPassword(): void 'with' => $middleRepository, ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -853,13 +947,15 @@ public function testLoginSuccess(): void 'with' => $middleRepository, ]); $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); $o = new JWTAuthentication( + $hash, + $userRepository, + $app, $jwt, $logger, $config, - $hash, - $userRepository, $tokenRepository, $containter, $cacheStorage @@ -873,4 +969,138 @@ public function testLoginSuccess(): void $data = $o->login($credentials); $this->assertCount(4, $data); } + + public function testReloginUserNotFound(): void + { + $identity = $this->getMockInstance(User::class, [ + 'getId' => 1, + ]); + $jwt = $this->getMockInstance(JWT::class); + $logger = $this->getMockInstance(Logger::class); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['api.auth.headers.name', 'Authorization', 'Authorization'], + ] + ]); + $tokenRepository = $this->getMockInstance(TokenRepository::class); + $hash = $this->getMockInstance(BcryptHash::class); + $request = $this->getMockInstanceMap(ServerRequest::class, [ + 'getHeaderLine' => [ + ['Authorization', ''] + ] + ]); + $containter = $this->getMockInstanceMap(Container::class, [ + 'has' => [ + [ServerRequestInterface::class, true], + ], + 'get' => [ + [ServerRequestInterface::class, $request], + ], + ]); + $userRepository = $this->getMockInstance(UserRepository::class, [ + 'findBy' => null + ]); + $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); + + $o = new JWTAuthentication( + $hash, + $userRepository, + $app, + $jwt, + $logger, + $config, + $tokenRepository, + $containter, + $cacheStorage + ); + $this->expectException(AccountNotFoundException::class); + + $o->relogin($identity); + } + + public function testReloginSuccess(): void + { + $identity = $this->getMockInstance(User::class, [ + 'getId' => 1, + ]); + $permission = $this->getMockInstanceMap(Permission::class, [ + '__get' => [ + ['code', 'foocode'] + ] + ]); + + $dt = new DateTime(); + + $token = $this->getMockInstanceMap(Token::class, [ + '__get' => [ + ['expire_at', $dt] + ] + ]); + + $role = $this->getMockInstanceMap(Role::class, [ + '__get' => [ + ['permissions', [$permission]] + ] + ]); + $user = $this->getMockInstanceMap(User::class, [ + '__get' => [ + ['id', 1], + ['password', 'password'], + ['status', 'A'], + ['roles', [$role]] + ] + ]); + $jwt = $this->getMockInstance(JWT::class); + $logger = $this->getMockInstance(Logger::class); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['api.auth.headers.name', 'Authorization', 'Authorization'], + ['api.auth.headers.token_type', 'Bearer', 'Bearer'], + ['api.sign.secret', null, 'foosecret'], + ['api.auth.token_expire', 900, 900], + ['api.auth.refresh_token_expire', 30 * 86400, 900], + ] + ]); + + $tokenRepository = $this->getMockInstance(TokenRepository::class, [ + 'create' => $token + ]); + $hash = $this->getMockInstance(BcryptHash::class, [ + 'verify' => true + ]); + $request = $this->getMockInstanceMap(ServerRequest::class, [ + 'getHeaderLine' => [ + ['Authorization', ''] + ] + ]); + $containter = $this->getMockInstanceMap(Container::class, [ + 'has' => [ + [ServerRequestInterface::class, true], + ], + 'get' => [ + [ServerRequestInterface::class, $request], + ], + ]); + $userRepository = $this->getMockInstance(UserRepository::class, [ + 'find' => $user, + ]); + $cacheStorage = $this->getMockInstance(NullCacheStorage::class); + $app = $this->getMockInstance(Application::class); + + $o = new JWTAuthentication( + $hash, + $userRepository, + $app, + $jwt, + $logger, + $config, + $tokenRepository, + $containter, + $cacheStorage + ); + + $data = $o->relogin($identity); + $this->assertCount(4, $data); + } } diff --git a/tests/Auth/Authentication/SessionAuthenticationTest.php b/tests/Auth/Authentication/SessionAuthenticationTest.php index 768b3f5..2d2dca5 100644 --- a/tests/Auth/Authentication/SessionAuthenticationTest.php +++ b/tests/Auth/Authentication/SessionAuthenticationTest.php @@ -34,7 +34,7 @@ public function testGetUserNotLogged(): void ]); $userRepository = $this->getMockInstance(UserRepository::class); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(AccountNotFoundException::class); $o->getUser(); } @@ -51,7 +51,7 @@ public function testGetUserNotFound(): void 'find' => null ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(AccountNotFoundException::class); $o->getUser(); } @@ -69,7 +69,7 @@ public function testGetUserSuccess(): void 'find' => $user ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->assertInstanceOf(User::class, $o->getUser()); } @@ -83,7 +83,7 @@ public function testGetPermissions(): void ]); $userRepository = $this->getMockInstance(UserRepository::class); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->assertCount(2, $o->getPermissions()); } @@ -100,7 +100,7 @@ public function testGetIdSuccess(): void 'find' => $user ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->assertEquals(1, $o->getId()); } @@ -117,7 +117,7 @@ public function testGetIdNotLogged(): void 'find' => $user ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(AccountNotFoundException::class); $o->getId(); } @@ -133,7 +133,7 @@ public function testLoginUsernameEmpty(): void 'find' => null ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(MissingCredentialsException::class); $credentials = [ @@ -153,7 +153,7 @@ public function testLoginPasswordEmpty(): void 'find' => null ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(MissingCredentialsException::class); $credentials = [ @@ -173,7 +173,7 @@ public function testLoginUserNotFound(): void 'findBy' => null ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(AccountNotFoundException::class); $credentials = [ @@ -201,7 +201,7 @@ public function testLoginUserIsLocked(): void 'with' => $middleRepository, ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(AccountLockedException::class); $credentials = [ @@ -231,7 +231,7 @@ public function testLoginWrongPassword(): void 'with' => $middleRepository, ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $this->expectException(InvalidCredentialsException::class); $credentials = [ @@ -276,7 +276,7 @@ public function testLoginSuccess(): void 'with' => $middleRepository, ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $credentials = [ 'username' => 'foo', 'password' => 'foo', @@ -284,6 +284,59 @@ public function testLoginSuccess(): void $this->assertCount(2, $o->login($credentials)); } + public function testReloginUserNotFound(): void + { + $identity = $this->getMockInstance(User::class, [ + 'getId' => 1, + ]); + $app = $this->getMockInstance(Application::class); + $hash = $this->getMockInstance(BcryptHash::class); + $session = $this->getMockInstance(Session::class); + $userRepository = $this->getMockInstance(UserRepository::class, [ + 'find' => null + ]); + + $o = new SessionAuthentication($hash, $userRepository, $app, $session); + $this->expectException(AccountNotFoundException::class); + + $o->relogin($identity); + } + + public function testReloginSuccess(): void + { + $identity = $this->getMockInstance(User::class, [ + 'getId' => 1, + ]); + $permission = $this->getMockInstanceMap(Permission::class, [ + '__get' => [ + ['code', 'foocode'] + ] + ]); + + $role = $this->getMockInstanceMap(Role::class, [ + '__get' => [ + ['permissions', [$permission]] + ] + ]); + $user = $this->getMockInstanceMap(User::class, [ + '__get' => [ + ['password', 'password'], + ['status', 'A'], + ['roles', [$role]] + ] + ]); + + $app = $this->getMockInstance(Application::class); + $hash = $this->getMockInstance(BcryptHash::class); + $session = $this->getMockInstance(Session::class); + $userRepository = $this->getMockInstance(UserRepository::class, [ + 'find' => $user, + ]); + + $o = new SessionAuthentication($hash, $userRepository, $app, $session); + $this->assertCount(2, $o->relogin($identity)); + } + public function testLogout(): void { global $mock_session_unset, $mock_session_destroy; @@ -305,7 +358,7 @@ public function testLogout(): void $userRepository = $this->getMockInstance(UserRepository::class, [ ]); - $o = new SessionAuthentication($app, $hash, $session, $userRepository); + $o = new SessionAuthentication($hash, $userRepository, $app, $session); $o->logout(); } } diff --git a/tests/Console/Command/InfoCommandTest.php b/tests/Console/Command/InfoCommandTest.php new file mode 100644 index 0000000..e62e7d4 --- /dev/null +++ b/tests/Console/Command/InfoCommandTest.php @@ -0,0 +1,60 @@ +createVfsFile('composer.lock', $this->vfsPath, '{"packages": [ + { + "name": "dompdf/dompdf", + "version": "v3.1.4", + "type": "library", + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "time": "2025-10-29T12:43:30+00:00" + }]}'); + + $writer = $this->getWriterInstance(); + $interactor = $this->getMockInstance(Interactor::class, [ + 'writer' => $writer + ]); + $app = $this->getMockInstance(Application::class, [ + 'io' => $interactor + ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['app', [], [ + 'name' => 'Foo', + 'debug' => false + ]], + ] + ]); + $application = $this->getMockInstance(PlatineApplication::class, [ + 'getRootPath' => $this->vfsPath->url(), + 'version' => '2.0.0-dev', + 'getEnvironment' => 'staging', + ]); + + $o = new InfoCommand($config, $application); + $o->bind($app); + $o->parse(['platine', 'info']); + $this->assertEquals('info', $o->getName()); + $o->execute(); + $this->assertStringContainsString('staging', $this->getConsoleOutputContent()); + } +} diff --git a/tests/Console/Command/MakeDatabaseConfigCommandTest.php b/tests/Console/Command/MakeDatabaseConfigCommandTest.php index 9e712cf..9be2581 100644 --- a/tests/Console/Command/MakeDatabaseConfigCommandTest.php +++ b/tests/Console/Command/MakeDatabaseConfigCommandTest.php @@ -110,9 +110,17 @@ public function testExecuteByModule(): void ['type', 'string'], ] ]); + + $cfgEntityDuplicate = $this->getMockInstanceMap(Entity::class, [ + '__get' => [ + ['module', 'app'], + ['name', 'foo'], + ['type', 'string'], + ] + ]); $dbLoader = $this->getMockInstance(DatabaseConfigLoader::class, [ - 'all' => [$cfgEntity], + 'all' => [$cfgEntity, $cfgEntityDuplicate], ]); $dbConfig = $this->getMockInstance(AppDatabaseConfig::class, [ 'getLoader' => $dbLoader, diff --git a/tests/Env/EnvTest.php b/tests/Env/EnvTest.php index 37a0899..74fc5b3 100644 --- a/tests/Env/EnvTest.php +++ b/tests/Env/EnvTest.php @@ -60,6 +60,25 @@ public function testGetUsingArray(): void $this->assertEquals('foo', $res1[0]); } + public function testGetUsingDuration(): void + { + $_ENV['default'] = '34'; + $_ENV['ms'] = '367090ms'; + $_ENV['s'] = '64s'; + $_ENV['m'] = '5m'; + $_ENV['h'] = '4h'; + $_ENV['w'] = '2w'; + $_ENV['unknow'] = '34i'; + + $this->assertEquals(34, Env::get('default', null, 'duration')); + $this->assertEquals(367, Env::get('ms', null, 'duration')); + $this->assertEquals(64, Env::get('s', null, 'duration')); + $this->assertEquals(300, Env::get('m', null, 'duration')); + $this->assertEquals(14400, Env::get('h', null, 'duration')); + $this->assertEquals(1209600, Env::get('w', null, 'duration')); + $this->assertEquals(34, Env::get('unknow', null, 'duration')); + } + public function testGetUsingArgumentValue(): void { global $mock_preg_replace_callback_to_null; diff --git a/tests/Helper/ActionHelperTest.php b/tests/Helper/ActionHelperTest.php index 6e141cd..7cdd424 100644 --- a/tests/Helper/ActionHelperTest.php +++ b/tests/Helper/ActionHelperTest.php @@ -35,16 +35,16 @@ public function testAll(): void $context = $this->getMockInstance(ViewContext::class); $o = new ActionHelper( $pagination, - $sidebar, $context, - $template, $routeHelper, - $flash, $lang, $logger, $auditor, $fileHelper, - $config + $config, + $sidebar, + $template, + $flash, ); $this->assertInstanceOf(ActionHelper::class, $o); diff --git a/tests/Helper/EntityHelperTest.php b/tests/Helper/EntityHelperTest.php index bd240bd..a40604d 100644 --- a/tests/Helper/EntityHelperTest.php +++ b/tests/Helper/EntityHelperTest.php @@ -128,29 +128,13 @@ public function testGetAttributeChanges(): void $this->assertEquals('Bar', $changes[1]['new']); } - public function testSubscribeEventsNotLogged(): void - { - $auditor = $this->getMockInstance(Auditor::class); - $authentication = $this->getMockInstance(SessionAuthentication::class, [ - 'isLogged' => false, - ]); - $mapper = $this->getMockInstance(EntityMapper::class); - $o = new EntityHelper($auditor, $authentication); - - $this->expectMethodCallCount($authentication, 'isLogged'); - $o->subscribeEvents($mapper); - } - public function testSubscribeEventsSuccess(): void { $auditor = $this->getMockInstance(Auditor::class); - $authentication = $this->getMockInstance(SessionAuthentication::class, [ - 'isLogged' => true, - ]); + $authentication = $this->getMockInstance(SessionAuthentication::class); $mapper = $this->getMockInstance(EntityMapper::class); $o = new EntityHelper($auditor, $authentication); - $this->expectMethodCallCount($authentication, 'isLogged'); $this->expectMethodCallCount($mapper, 'on', 3); $o->subscribeEvents($mapper); } @@ -165,6 +149,11 @@ public function testSubscribeEventsCreateNotIgnore(): void $this->subscribeEventsRecordChange(false); } + public function testSubscribeEventsCreateNotIgnoreNotLogged(): void + { + $this->subscribeEventsRecordChange(false, true, false, false); + } + public function testSubscribeEventsUpdateIgnore(): void { $this->subscribeEventsRecordChange(true, false); @@ -175,6 +164,11 @@ public function testSubscribeEventsUpdateNotIgnore(): void $this->subscribeEventsRecordChange(false, false); } + public function testSubscribeEventsUpdateNotIgnoreNotLogged(): void + { + $this->subscribeEventsRecordChange(false, false, false, false); + } + public function testSubscribeEventsDeleteIgnore(): void { $this->subscribeEventsRecordChange(true, false, true); @@ -185,7 +179,12 @@ public function testSubscribeEventsDeleteNotIgnore(): void $this->subscribeEventsRecordChange(false, false, true); } - private function subscribeEventsRecordChange(bool $ignore, bool $create = true, bool $delete = false): void + public function testSubscribeEventsDeleteNotIgnoreNotLogged(): void + { + $this->subscribeEventsRecordChange(false, false, true, false); + } + + private function subscribeEventsRecordChange(bool $ignore, bool $create = true, bool $delete = false, bool $isLogged = true): void { $resultSetFinal = $this->getMockInstance(ResultSet::class, [ 'get' => ['name' => 'foo', 'id' => 1], @@ -218,13 +217,13 @@ private function subscribeEventsRecordChange(bool $ignore, bool $create = true, $auditor = $this->getMockInstance(Auditor::class); $authentication = $this->getMockInstance(SessionAuthentication::class, [ - 'isLogged' => true, + 'isLogged' => $isLogged, ]); $o = new EntityHelper($auditor, $authentication); $o->setIgnore($ignore); - $this->expectMethodCallCount($auditor, 'setDetail', $ignore ? 0 : 1); + $this->expectMethodCallCount($auditor, 'setDetail', $ignore === false && $isLogged ? 1 : 0); $o->subscribeEvents($mapper); diff --git a/tests/Http/Action/BaseActionTest.php b/tests/Http/Action/BaseActionTest.php index 8b71a95..3030c24 100644 --- a/tests/Http/Action/BaseActionTest.php +++ b/tests/Http/Action/BaseActionTest.php @@ -4,23 +4,19 @@ namespace Platine\Test\Framework\Http\Action; +use Exception; +use Platine\Config\Config; use Platine\Dev\PlatineTestCase; -use Platine\Framework\Auth\Repository\UserRepository; use Platine\Framework\Helper\ActionHelper; use Platine\Framework\Helper\Sidebar; use Platine\Framework\Http\Action\BaseAction; use Platine\Framework\Http\Response\RedirectResponse; -use Platine\Framework\Http\Response\RestResponse; use Platine\Framework\Http\Response\TemplateResponse; use Platine\Framework\Http\RouteHelper; use Platine\Framework\Security\SecurityPolicy; use Platine\Http\ServerRequest; -use Platine\Lang\Lang; use Platine\Logger\Logger; -use Platine\Orm\Query\EntityQuery; -use Platine\Pagination\Pagination; use Platine\Test\Framework\Fixture\MyBaseAction; -use Platine\Test\Framework\Fixture\MyBaseAction2; class BaseActionTest extends PlatineTestCase { @@ -36,6 +32,7 @@ public function testConstruct(): void $this->assertInstanceOf(MyBaseAction::class, $o); } + public function testHandleBypassPagination(): void { $this->handle(true); @@ -46,6 +43,7 @@ public function testHandleDoNotBypassPagination(): void $this->handle(false); } + public function testRedirect(): void { global $mock_app_httpaction_to_instance; @@ -61,16 +59,13 @@ public function testRedirect(): void ]); $actionHelper = $this->createObject(ActionHelper::class); - $o = new MyBaseAction2($actionHelper); + $o = new MyBaseAction($actionHelper); $this->expectMethodCallCount($routeHelper, 'generateUrl'); $resp = $this->runPrivateProtectedMethod($o, 'redirect', ['user_create', [], ['foo' => 'bar']]); $this->assertInstanceOf(RedirectResponse::class, $resp); $this->assertEquals(302, $resp->getStatusCode()); $this->assertEquals('/user/create?foo=bar', $resp->getHeaderLine('location')); - - // Why put this here? - $this->assertEmpty($this->runPrivateProtectedMethod($o, 'getIgnoreDateFilters', [])); } public function testRedirectBackToOriginMissingRoute(): void @@ -88,336 +83,6 @@ public function testRedirectBackToOriginIdProvided(): void $this->redirectBackToOrigin('1', 'user_detail'); } - public function testParseForeignConstraintErrorMessageMySQL(): void - { - global $mock_app_httpaction_to_instance; - $mock_app_httpaction_to_instance = true; - - $logger = $this->getMockInstance(Logger::class); - $lang = $this->getMockInstance(Lang::class, [ - 'tr' => 'mysql error lang', - ]); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - 'lang' => $lang, - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - - $error = $this->runPrivateProtectedMethod( - $o, - 'parseForeignConstraintErrorMessage', - [ - 'SQLSTATE[23000]: Integrity constraint violation: 1217 Cannot delete or update a parent row' - ] - ); - $this->assertEquals('mysql error lang', $error); - } - - public function testParseForeignConstraintErrorMessageMariaDB(): void - { - global $mock_app_httpaction_to_instance; - $mock_app_httpaction_to_instance = true; - - $logger = $this->getMockInstance(Logger::class); - $lang = $this->getMockInstance(Lang::class, [ - 'tr' => 'mariadb error lang', - ]); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - 'lang' => $lang, - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - - $error = $this->runPrivateProtectedMethod( - $o, - 'parseForeignConstraintErrorMessage', - [ - 'SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row' - ] - ); - $this->assertEquals('mariadb error lang', $error); - } - - public function testHandleRestPaginationDefaultSort(): void - { - $this->handleRestPagination(true); - } - - public function testHandleRestPaginationQueryParamSort(): void - { - $this->handleRestPagination(false); - } - - private function handleRestPagination(bool $defaultSort = false): void - { - $query = $this->getMockInstance(EntityQuery::class, [ - 'count' => 23, - ]); - $repo1 = $this->getMockInstance(UserRepository::class, [ - 'query' => $query, - ]); - $repo = $this->getMockInstance(UserRepository::class, [ - 'filters' => $repo1, - ]); - $logger = $this->getMockInstance(Logger::class); - $pagination = $this->getMockInstance(Pagination::class, [ - 'getTotalItems' => 16, - 'getInfo' => ['page' => 1], - ]); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - 'pagination' => $pagination, - ]); - $request = $this->getMockInstanceMap(ServerRequest::class, [ - 'getQueryParams' => [ - [[ - 'sort' => $defaultSort === false ? 'name:desc,status' : '', - 'page' => 1, - 'limit' => 101, - ]] - ], - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - /** @var RestResponse $resp */ - $resp = $o->handle($request); - - $this->expectMethodCallCount($repo, 'filters'); - $this->expectMethodCallCount($query, 'count'); - $this->expectMethodCallCount($pagination, 'setTotalItems'); - $this->runPrivateProtectedMethod( - $o, - 'handleRestPagination', - [$repo, $query] - ); - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertEquals('OK', $resp->getReasonPhrase()); - } - - public function testRestResponse(): void - { - global $mock_time_to_1000; - $mock_time_to_1000 = true; - - $logger = $this->getMockInstance(Logger::class); - $pagination = $this->getMockInstance(Pagination::class, [ - 'getTotalItems' => 16, - 'getInfo' => ['page' => 1], - ]); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - 'pagination' => $pagination, - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - /** @var RestResponse $resp */ - $resp = $this->runPrivateProtectedMethod( - $o, - 'restResponse', - [['foo' => 'bar'], 201, 0] - ); - $this->assertInstanceOf(RestResponse::class, $resp); - $this->assertEquals(201, $resp->getStatusCode()); - $this->assertEquals('Created', $resp->getReasonPhrase()); - $this->assertEquals(87, $resp->getBody()->getSize()); - $resp->getBody()->rewind(); - $this->assertEquals( - '{"success":true,"timestamp":1000,"code":0,"data":{"foo":"bar"},"pagination":{"page":1}}', - $resp->getBody()->getContents() - ); - } - - public function testRestCreatedResponse(): void - { - global $mock_time_to_1000; - $mock_time_to_1000 = true; - - $logger = $this->getMockInstance(Logger::class); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - /** @var RestResponse $resp */ - $resp = $this->runPrivateProtectedMethod( - $o, - 'restCreatedResponse', - [['foo' => 'bar'], 0] - ); - $this->assertInstanceOf(RestResponse::class, $resp); - $this->assertEquals(201, $resp->getStatusCode()); - $this->assertEquals('Created', $resp->getReasonPhrase()); - $this->assertEquals(63, $resp->getBody()->getSize()); - $resp->getBody()->rewind(); - $this->assertEquals( - '{"success":true,"timestamp":1000,"code":0,"data":{"foo":"bar"}}', - $resp->getBody()->getContents() - ); - } - - public function testRestNoContentResponse(): void - { - global $mock_time_to_1000; - $mock_time_to_1000 = true; - - $logger = $this->getMockInstance(Logger::class); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - /** @var RestResponse $resp */ - $resp = $this->runPrivateProtectedMethod( - $o, - 'restNoContentResponse', - [0] - ); - $this->assertInstanceOf(RestResponse::class, $resp); - $this->assertEquals(204, $resp->getStatusCode()); - $this->assertEquals('No Content', $resp->getReasonPhrase()); - $this->assertEquals(52, $resp->getBody()->getSize()); - $resp->getBody()->rewind(); - $this->assertEquals( - '{"success":true,"timestamp":1000,"code":0,"data":[]}', - $resp->getBody()->getContents() - ); - } - - public function testRestErrorResponse(): void - { - global $mock_time_to_1000; - $mock_time_to_1000 = true; - - $logger = $this->getMockInstance(Logger::class); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - /** @var RestResponse $resp */ - $resp = $this->runPrivateProtectedMethod( - $o, - 'restErrorResponse', - ['Error response'] - ); - $this->assertInstanceOf(RestResponse::class, $resp); - $this->assertEquals(401, $resp->getStatusCode()); - $this->assertEquals(86, $resp->getBody()->getSize()); - $resp->getBody()->rewind(); - $this->assertEquals( - '{"success":false,"timestamp":1000,"code":"ERROR","message":"Error response","data":[]}', - $resp->getBody()->getContents() - ); - } - - public function testAllRestErrorResponse(): void - { - global $mock_time_to_1000; - $mock_time_to_1000 = true; - - $logger = $this->getMockInstance(Logger::class); - $lang = $this->getMockInstance(Lang::class, ['tr' => 'lang msg']); - $this->setClassCreateObjectMaps(ActionHelper::class, [ - 'logger' => $logger, - 'lang' => $lang, - ]); - $actionHelper = $this->createObject(ActionHelper::class); - - $o = new MyBaseAction($actionHelper); - - $resp1 = $this->runPrivateProtectedMethod( - $o, - 'restServerErrorResponse', - ['Server Error'] - ); - $this->assertEquals(500, $resp1->getStatusCode()); - $this->assertEquals(100, $resp1->getBody()->getSize()); - $resp1->getBody()->rewind(); - $this->assertEquals( - '{"success":false,"timestamp":1000,"code":"INTERNAL_SERVER_ERROR","message":"Server Error","data":[]}', - $resp1->getBody()->getContents() - ); - - // - $resp2 = $this->runPrivateProtectedMethod( - $o, - 'restBadRequestErrorResponse', - ['Bad Error'] - ); - $this->assertEquals(400, $resp2->getStatusCode()); - $this->assertEquals(87, $resp2->getBody()->getSize()); - $resp2->getBody()->rewind(); - $this->assertEquals( - '{"success":false,"timestamp":1000,"code":"BAD_REQUEST","message":"Bad Error","data":[]}', - $resp2->getBody()->getContents() - ); - - // - $resp3 = $this->runPrivateProtectedMethod( - $o, - 'restConflictErrorResponse', - ['Conflict Error'] - ); - $this->assertEquals(409, $resp3->getStatusCode()); - $this->assertEquals(99, $resp3->getBody()->getSize()); - $resp3->getBody()->rewind(); - $this->assertEquals( - '{"success":false,"timestamp":1000,"code":"DUPLICATE_RESOURCE","message":"Conflict Error","data":[]}', - $resp3->getBody()->getContents() - ); - - // - $resp4 = $this->runPrivateProtectedMethod( - $o, - 'restNotFoundErrorResponse', - ['Not Found Error'] - ); - $this->assertEquals(404, $resp4->getStatusCode()); - $this->assertEquals(100, $resp4->getBody()->getSize()); - $resp4->getBody()->rewind(); - $this->assertEquals( - '{"success":false,"timestamp":1000,"code":"RESOURCE_NOT_FOUND","message":"Not Found Error","data":[]}', - $resp4->getBody()->getContents() - ); - - // - $resp5 = $this->runPrivateProtectedMethod( - $o, - 'restFormValidationErrorResponse', - [['email' => 'invalid email address']] - ); - $this->assertEquals(422, $resp5->getStatusCode()); - $this->assertEquals(131, $resp5->getBody()->getSize()); - $resp5->getBody()->rewind(); - $this->assertEquals( - '{"success":false,"timestamp":1000,"code":"INVALID_INPUT","message":"lang msg","data":[],' - . '"errors":{"email":"invalid email address"}}', - $resp5->getBody()->getContents() - ); - - // - $resp6 = $this->runPrivateProtectedMethod( - $o, - 'restUnauthorizedErrorResponse', - ['User not login'] - ); - $this->assertEquals(401, $resp6->getStatusCode()); - $this->assertEquals(100, $resp6->getBody()->getSize()); - $resp6->getBody()->rewind(); - $this->assertEquals( - '{"success":false,"timestamp":1000,"code":"UNAUTHORIZED_ACCESS","message":"User not login","data":[]}', - $resp6->getBody()->getContents() - ); - } protected function handle(bool $bypassPagination = false): void { @@ -446,7 +111,7 @@ protected function handle(bool $bypassPagination = false): void 'fields' => 'name,status', 'sort' => 'name:desc,status', 'page' => 1, - 'limit' => 101, + 'limit' => 200, 'status' => 'Y', 'start_date' => '2025-09-01', 'end_date' => '2025-09-30', @@ -456,9 +121,17 @@ protected function handle(bool $bypassPagination = false): void ]] ], ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); + $this->setClassCreateObjectMaps(ActionHelper::class, [ 'logger' => $logger, 'sidebar' => $sidebar, + 'config' => $config, ]); $actionHelper = $this->createObject(ActionHelper::class); @@ -473,24 +146,15 @@ protected function handle(bool $bypassPagination = false): void $this->assertEquals(200, $resp->getStatusCode()); $this->assertEquals( - $bypassPagination ? null : 100, + $bypassPagination ? 1000 : 100, $this->getPropertyValue(BaseAction::class, $o, 'limit') ); $this->assertEquals('foo_view', $this->getPropertyValue(BaseAction::class, $o, 'viewName')); $this->assertEquals( - $bypassPagination ? null : 1, + $bypassPagination ? 1 : 1, $this->getPropertyValue(BaseAction::class, $o, 'page') ); - $this->assertEquals( - $bypassPagination ? true : false, - $this->getPropertyValue(BaseAction::class, $o, 'all') - ); - - $this->assertEquals( - ['name' => 'DESC', 'status' => 'ASC'], - $this->getPropertyValue(BaseAction::class, $o, 'sorts') - ); $this->assertEquals( ['permissions' => [5,7], 'status' => 'Y', 'multi' => [5, 7]], $this->getPropertyValue(BaseAction::class, $o, 'filters') @@ -514,7 +178,15 @@ protected function redirectBackToOrigin(string $originId = '0', ?string $originR ]] ], ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, 'logger' => $logger, 'routeHelper' => $routeHelper, ]); @@ -531,4 +203,42 @@ protected function redirectBackToOrigin(string $originId = '0', ?string $originR $this->assertNull($resp); } } + + public function testLogRequestException(): void + { + global $mock_app_httpaction_to_instance; + $mock_app_httpaction_to_instance = true; + + $logger = $this->getMockInstance(Logger::class); + $routeHelper = $this->getMockInstance(RouteHelper::class, [ + 'generateUrl' => '/user/create', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getHeaderLine' => 'X-Request-ID' + ]); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); + + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, + 'logger' => $logger, + 'routeHelper' => $routeHelper, + ]); + + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyBaseAction($actionHelper); + $o->handle($request); + + $ex = new Exception(); + + $this->expectMethodCallCount($logger, 'error'); + $this->expectMethodCallCount($request, 'getHeaderLine'); + $this->runPrivateProtectedMethod($o, 'logRequestException', [$ex]); + } } diff --git a/tests/Http/Action/BaseConfigurationActionTest.php b/tests/Http/Action/BaseConfigurationActionTest.php index 7951a2b..9de58a5 100644 --- a/tests/Http/Action/BaseConfigurationActionTest.php +++ b/tests/Http/Action/BaseConfigurationActionTest.php @@ -4,6 +4,7 @@ namespace Platine\Test\Framework\Http\Action; +use Platine\Config\Config; use Platine\Dev\PlatineTestCase; use Platine\Framework\Config\AppDatabaseConfig; use Platine\Framework\Config\DatabaseConfigLoader; @@ -64,7 +65,15 @@ public function testRespondRequestMethodGet(): void ['GET'], ], ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, 'logger' => $logger, ]); $actionHelper = $this->createObject(ActionHelper::class); @@ -77,8 +86,7 @@ public function testRespondRequestMethodGet(): void $this->assertEquals(200, $resp->getStatusCode()); $this->assertEquals('', $this->getPropertyValue(BaseAction::class, $o, 'viewName')); - $this->assertEquals(null, $this->getPropertyValue(BaseAction::class, $o, 'page')); - $this->assertFalse($this->getPropertyValue(BaseAction::class, $o, 'all')); + $this->assertEquals(1, $this->getPropertyValue(BaseAction::class, $o, 'page')); } public function testRespondSaveFormValidationFailed(): void @@ -100,7 +108,14 @@ public function testRespondSaveFormValidationFailed(): void ['POST'], ], ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, 'logger' => $logger, 'context' => $viewContext, ]); @@ -114,8 +129,7 @@ public function testRespondSaveFormValidationFailed(): void $this->assertInstanceOf(TemplateResponse::class, $resp); $this->assertEquals(200, $resp->getStatusCode()); $this->assertEquals('', $this->getPropertyValue(BaseAction::class, $o, 'viewName')); - $this->assertEquals(null, $this->getPropertyValue(BaseAction::class, $o, 'page')); - $this->assertFalse($this->getPropertyValue(BaseAction::class, $o, 'all')); + $this->assertEquals(1, $this->getPropertyValue(BaseAction::class, $o, 'page')); } public function testRespondSaveSuccess(): void @@ -151,7 +165,14 @@ public function testRespondSaveSuccess(): void [['name' => 'foo', 'status' => 'bar']], ], ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, 'logger' => $logger, 'context' => $viewContext, ]); @@ -168,7 +189,6 @@ public function testRespondSaveSuccess(): void $this->assertInstanceOf(RedirectResponse::class, $resp); $this->assertEquals(302, $resp->getStatusCode()); $this->assertEquals('', $this->getPropertyValue(BaseAction::class, $o, 'viewName')); - $this->assertEquals(null, $this->getPropertyValue(BaseAction::class, $o, 'page')); - $this->assertFalse($this->getPropertyValue(BaseAction::class, $o, 'all')); + $this->assertEquals(1, $this->getPropertyValue(BaseAction::class, $o, 'page')); } } diff --git a/tests/Http/Action/BaseResourceActionTest.php b/tests/Http/Action/BaseResourceActionTest.php index a72c86d..8a24ee5 100644 --- a/tests/Http/Action/BaseResourceActionTest.php +++ b/tests/Http/Action/BaseResourceActionTest.php @@ -4,6 +4,7 @@ namespace Platine\Test\Framework\Http\Action; +use Platine\Config\Config; use Platine\Dev\PlatineTestCase; use Platine\Framework\Helper\ActionHelper; use Platine\Framework\Http\Exception\HttpNotFoundException; @@ -22,7 +23,14 @@ public function testResponseNotFound(): void $request = $this->getMockInstance(ServerRequest::class, [ 'getAttribute' => $route, ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, 'logger' => $logger, ]); $actionHelper = $this->createObject(ActionHelper::class); @@ -40,7 +48,14 @@ public function testResponseSucess(): void $request = $this->getMockInstance(ServerRequest::class, [ 'getAttribute' => $route, ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, 'logger' => $logger, ]); $actionHelper = $this->createObject(ActionHelper::class); diff --git a/tests/Http/Action/RestBaseActionTest.php b/tests/Http/Action/RestBaseActionTest.php new file mode 100644 index 0000000..ab152fa --- /dev/null +++ b/tests/Http/Action/RestBaseActionTest.php @@ -0,0 +1,413 @@ +getMockInstance(Logger::class); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'logger' => $logger, + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + $this->assertInstanceOf(MyRestBaseAction::class, $o); + } + + public function testHandleBypassPagination(): void + { + $this->handle(true); + } + + public function testHandleDoNotBypassPagination(): void + { + $this->handle(false); + } + + + public function testHandleRestPaginationDefaultSort(): void + { + $this->handleRestPagination(true); + } + + public function testHandleRestPaginationQueryParamSort(): void + { + $this->handleRestPagination(false); + } + + private function handleRestPagination(bool $defaultSort = false): void + { + $query = $this->getMockInstance(EntityQuery::class, [ + 'count' => 23, + ]); + $repo1 = $this->getMockInstance(UserRepository::class, [ + 'query' => $query, + ]); + $repo = $this->getMockInstance(UserRepository::class, [ + 'filters' => $repo1, + ]); + $logger = $this->getMockInstance(Logger::class); + $pagination = $this->getMockInstance(Pagination::class, [ + 'getTotalItems' => 16, + 'getInfo' => ['page' => 1], + ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, + 'logger' => $logger, + 'pagination' => $pagination, + ]); + $request = $this->getMockInstanceMap(ServerRequest::class, [ + 'getQueryParams' => [ + [[ + 'sort' => $defaultSort === false ? 'name:desc,status' : '', + 'page' => 1, + 'limit' => 101, + ]] + ], + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + /** @var RestResponse $resp */ + $resp = $o->handle($request); + + $this->expectMethodCallCount($repo, 'filters'); + $this->expectMethodCallCount($query, 'count'); + $this->expectMethodCallCount($pagination, 'setTotalItems'); + $this->runPrivateProtectedMethod( + $o, + 'handleRestPagination', + [$repo, $query] + ); + $this->assertEquals(200, $resp->getStatusCode()); + $this->assertEquals('OK', $resp->getReasonPhrase()); + } + + public function testRestResponse(): void + { + global $mock_time_to_1000; + $mock_time_to_1000 = true; + + $logger = $this->getMockInstance(Logger::class); + $pagination = $this->getMockInstance(Pagination::class, [ + 'getTotalItems' => 16, + 'getInfo' => ['page' => 1], + ]); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'logger' => $logger, + 'pagination' => $pagination, + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + /** @var RestResponse $resp */ + $resp = $this->runPrivateProtectedMethod( + $o, + 'restResponse', + [['foo' => 'bar'], 201, 0] + ); + $this->assertInstanceOf(RestResponse::class, $resp); + $this->assertEquals(201, $resp->getStatusCode()); + $this->assertEquals('Created', $resp->getReasonPhrase()); + $this->assertEquals(87, $resp->getBody()->getSize()); + $resp->getBody()->rewind(); + $this->assertEquals( + '{"success":true,"timestamp":1000,"code":0,"data":{"foo":"bar"},"pagination":{"page":1}}', + $resp->getBody()->getContents() + ); + } + + public function testRestCreatedResponse(): void + { + global $mock_time_to_1000; + $mock_time_to_1000 = true; + + $logger = $this->getMockInstance(Logger::class); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'logger' => $logger, + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + /** @var RestResponse $resp */ + $resp = $this->runPrivateProtectedMethod( + $o, + 'createdResponse', + [['foo' => 'bar'], 0] + ); + $this->assertInstanceOf(RestResponse::class, $resp); + $this->assertEquals(201, $resp->getStatusCode()); + $this->assertEquals('Created', $resp->getReasonPhrase()); + $this->assertEquals(63, $resp->getBody()->getSize()); + $resp->getBody()->rewind(); + $this->assertEquals( + '{"success":true,"timestamp":1000,"code":0,"data":{"foo":"bar"}}', + $resp->getBody()->getContents() + ); + } + + public function testRestNoContentResponse(): void + { + global $mock_time_to_1000; + $mock_time_to_1000 = true; + + $logger = $this->getMockInstance(Logger::class); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'logger' => $logger, + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + /** @var RestResponse $resp */ + $resp = $this->runPrivateProtectedMethod( + $o, + 'noContentResponse', + [0] + ); + $this->assertInstanceOf(RestResponse::class, $resp); + $this->assertEquals(204, $resp->getStatusCode()); + $this->assertEquals('No Content', $resp->getReasonPhrase()); + $this->assertEquals(52, $resp->getBody()->getSize()); + $resp->getBody()->rewind(); + $this->assertEquals( + '{"success":true,"timestamp":1000,"code":0,"data":[]}', + $resp->getBody()->getContents() + ); + } + + public function testRestErrorResponse(): void + { + global $mock_time_to_1000; + $mock_time_to_1000 = true; + + $logger = $this->getMockInstance(Logger::class); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'logger' => $logger, + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + /** @var RestResponse $resp */ + $resp = $this->runPrivateProtectedMethod( + $o, + 'errorResponse', + ['Error response'] + ); + $this->assertInstanceOf(RestResponse::class, $resp); + $this->assertEquals(401, $resp->getStatusCode()); + $this->assertEquals(86, $resp->getBody()->getSize()); + $resp->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"ERROR","message":"Error response","data":[]}', + $resp->getBody()->getContents() + ); + } + + public function testAllRestErrorResponse(): void + { + global $mock_time_to_1000; + $mock_time_to_1000 = true; + + $logger = $this->getMockInstance(Logger::class); + $lang = $this->getMockInstance(Lang::class, ['tr' => 'lang msg']); + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'logger' => $logger, + 'lang' => $lang, + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + + $resp1 = $this->runPrivateProtectedMethod( + $o, + 'internalServerErrorResponse', + ['Server Error'] + ); + $this->assertEquals(500, $resp1->getStatusCode()); + $this->assertEquals(100, $resp1->getBody()->getSize()); + $resp1->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"INTERNAL_SERVER_ERROR","message":"Server Error","data":[]}', + $resp1->getBody()->getContents() + ); + + // + $resp2 = $this->runPrivateProtectedMethod( + $o, + 'badRequestResponse', + ['Bad Error'] + ); + $this->assertEquals(400, $resp2->getStatusCode()); + $this->assertEquals(87, $resp2->getBody()->getSize()); + $resp2->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"BAD_REQUEST","message":"Bad Error","data":[]}', + $resp2->getBody()->getContents() + ); + + // + $resp3 = $this->runPrivateProtectedMethod( + $o, + 'conflictResponse', + ['Conflict Error'] + ); + $this->assertEquals(409, $resp3->getStatusCode()); + $this->assertEquals(99, $resp3->getBody()->getSize()); + $resp3->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"DUPLICATE_RESOURCE","message":"Conflict Error","data":[]}', + $resp3->getBody()->getContents() + ); + + // + $resp4 = $this->runPrivateProtectedMethod( + $o, + 'notFoundResponse', + ['Not Found Error'] + ); + $this->assertEquals(404, $resp4->getStatusCode()); + $this->assertEquals(100, $resp4->getBody()->getSize()); + $resp4->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"RESOURCE_NOT_FOUND","message":"Not Found Error","data":[]}', + $resp4->getBody()->getContents() + ); + + // + $resp5 = $this->runPrivateProtectedMethod( + $o, + 'formValidationErrorResponse', + [['email' => 'invalid email address']] + ); + $this->assertEquals(422, $resp5->getStatusCode()); + $this->assertEquals(131, $resp5->getBody()->getSize()); + $resp5->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"INVALID_INPUT","message":"lang msg","data":[],' + . '"errors":{"email":"invalid email address"}}', + $resp5->getBody()->getContents() + ); + + // + $resp6 = $this->runPrivateProtectedMethod( + $o, + 'unauthorizedResponse', + ['User not login'] + ); + $this->assertEquals(401, $resp6->getStatusCode()); + $this->assertEquals(100, $resp6->getBody()->getSize()); + $resp6->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"UNAUTHORIZED_ACCESS","message":"User not login","data":[]}', + $resp6->getBody()->getContents() + ); + + $resp7 = $this->runPrivateProtectedMethod( + $o, + 'forbiddenResponse', + ['User not login'] + ); + $this->assertEquals(403, $resp7->getStatusCode()); + $this->assertEquals(90, $resp7->getBody()->getSize()); + $resp7->getBody()->rewind(); + $this->assertEquals( + '{"success":false,"timestamp":1000,"code":"FORBIDDEN","message":"User not login","data":[]}', + $resp7->getBody()->getContents() + ); + } + + protected function handle(bool $bypassPagination = false): void + { + global $mock_app_httpaction_to_instance; + $mock_app_httpaction_to_instance = true; + + $logger = $this->getMockInstance(Logger::class); + $request = $this->getMockInstanceMap(ServerRequest::class, [ + 'getAttribute' => [ + [ + SecurityPolicy::class, null, [ + 'nonces' => [ + 'style' => 'stylenonces', + 'script' => 'scriptnonces', + ], + ], + ], + ['csrf_token', null, 'mytoken'], + ], + 'getQueryParams' => [ + [[ + 'fields' => 'name,status', + 'sort' => 'name:desc,status', + 'page' => 1, + 'limit' => 101, + 'status' => 'Y', + 'start_date' => '2025-09-01', + 'end_date' => '2025-09-30', + 'permissions' => [5,7], + 'multi' => [5,7, null], + 'all' => $bypassPagination ? 1 : 0 + ]] + ], + ]); + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['pagination.max_per_page', 100, 100], + ['pagination.max_limit', 1000, 1000], + ], + ]); + + $this->setClassCreateObjectMaps(ActionHelper::class, [ + 'config' => $config, + 'logger' => $logger, + ]); + $actionHelper = $this->createObject(ActionHelper::class); + + $o = new MyRestBaseAction($actionHelper); + $this->expectMethodCallCount($request, 'getQueryParams'); + $resp = $o->handle($request); + $this->assertInstanceOf(RestResponse::class, $resp); + $this->assertEquals(200, $resp->getStatusCode()); + + $this->assertEquals( + $bypassPagination ? 1000 : 100, + $this->getPropertyValue(RestBaseAction::class, $o, 'limit') + ); + $this->assertEquals( + $bypassPagination ? 1 : 1, + $this->getPropertyValue(RestBaseAction::class, $o, 'page') + ); + + $this->assertEquals( + ['name' => 'DESC', 'status' => 'ASC'], + $this->getPropertyValue(RestBaseAction::class, $o, 'sorts') + ); + $this->assertEquals( + ['permissions' => [5,7], 'status' => 'Y', 'multi' => [5, 7]], + $this->getPropertyValue(RestBaseAction::class, $o, 'filters') + ); + } +} diff --git a/tests/Service/Provider/CommandServiceProviderTest.php b/tests/Service/Provider/CommandServiceProviderTest.php index 6235a94..6ee42d7 100644 --- a/tests/Service/Provider/CommandServiceProviderTest.php +++ b/tests/Service/Provider/CommandServiceProviderTest.php @@ -18,7 +18,7 @@ public function testRegister(): void { $app = $this->getMockInstanceMap(Application::class); - $app->expects($this->exactly(18)) + $app->expects($this->exactly(19)) ->method('bind'); $o = new CommandServiceProvider($app); diff --git a/tests/fixtures/fixtures.php b/tests/fixtures/fixtures.php index 48dfb80..df26a5d 100644 --- a/tests/fixtures/fixtures.php +++ b/tests/fixtures/fixtures.php @@ -22,6 +22,7 @@ use Platine\Framework\Http\Action\BaseConfigurationAction; use Platine\Framework\Http\Action\BaseResourceAction; use Platine\Framework\Http\Action\CrudAction; +use Platine\Framework\Http\Action\RestBaseAction; use Platine\Framework\Http\Maintenance\MaintenanceDriverInterface; use Platine\Framework\Http\Response\JsonResponse; use Platine\Framework\Http\RouteHelper; @@ -136,6 +137,22 @@ protected function getIgnoreDateFilters(): array } } +class MyRestBaseAction extends RestBaseAction +{ + public function respond(): ResponseInterface + { + $this->addContext('foo', 'bar'); + $this->addContexts(['name' => 'Tony']); + + return $this->restResponse(); + } + + protected function getIgnoreDateFilters(): array + { + return ['status']; + } +} + class MyBaseAction2 extends BaseAction { public function respond(): ResponseInterface