mirror of
https://github.com/nuxsmin/sysPass.git
synced 2026-03-02 22:54:08 +01:00
chore: Refactor upgrade subsystem
Use attributes to detect and load upgrade handlers. Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
@@ -35,6 +35,7 @@ use SP\Domain\Api\Ports\ApiRequestService;
|
||||
use SP\Domain\Api\Services\JsonRpcResponse;
|
||||
use SP\Domain\Core\Bootstrap\BootstrapInterface;
|
||||
use SP\Domain\Core\Bootstrap\ModuleInterface;
|
||||
use SP\Domain\Http\Code;
|
||||
|
||||
use function SP\logger;
|
||||
use function SP\processException;
|
||||
@@ -73,18 +74,17 @@ final class Bootstrap extends BootstrapBase
|
||||
try {
|
||||
logger('API route');
|
||||
|
||||
$apiRequest = $this->createObjectFor(ApiRequestService::class);
|
||||
$response->headers()->set('Content-type', 'application/json; charset=utf-8');
|
||||
|
||||
$apiRequest = $this->buildInstanceFor(ApiRequestService::class);
|
||||
[$controllerName, $actionName] = explode('/', $apiRequest->getMethod());
|
||||
|
||||
$controllerClass = self::getClassFor($controllerName, $actionName);
|
||||
|
||||
$method = $actionName . 'Action';
|
||||
|
||||
if (!method_exists($controllerClass, $method)) {
|
||||
logger($controllerClass . '::' . $method);
|
||||
|
||||
$response->headers()->set('Content-type', 'application/json; charset=utf-8');
|
||||
$response->code(Code::NOT_FOUND->value);
|
||||
|
||||
return $response->body(
|
||||
JsonRpcResponse::getResponseError(
|
||||
@@ -103,11 +103,11 @@ final class Bootstrap extends BootstrapBase
|
||||
|
||||
logger('Routing call: ' . $controllerClass . '::' . $method);
|
||||
|
||||
return call_user_func([$this->createObjectFor($controllerClass), $method]);
|
||||
return call_user_func([$this->buildInstanceFor($controllerClass), $method]);
|
||||
} catch (Exception $e) {
|
||||
processException($e);
|
||||
|
||||
$response->headers()->set('Content-type', 'application/json; charset=utf-8');
|
||||
$response->code(Code::INTERNAL_SERVER_ERROR->value);
|
||||
|
||||
return $response->body(JsonRpcResponse::getResponseException($e, 0));
|
||||
} finally {
|
||||
|
||||
@@ -30,13 +30,13 @@ use Klein\Request;
|
||||
use Klein\Response;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use RuntimeException;
|
||||
use SP\Core\Bootstrap\BootstrapBase;
|
||||
use SP\Core\Bootstrap\RouteContext;
|
||||
use SP\Domain\Common\Providers\Filter;
|
||||
use SP\Domain\Core\Bootstrap\BootstrapInterface;
|
||||
use SP\Domain\Core\Bootstrap\ModuleInterface;
|
||||
use SP\Domain\Core\Exceptions\SessionTimeout;
|
||||
use SP\Domain\Http\Code;
|
||||
|
||||
use function SP\__;
|
||||
use function SP\logger;
|
||||
@@ -87,9 +87,10 @@ final class Bootstrap extends BootstrapBase
|
||||
if (!method_exists($controllerClass, $routeContextData->getMethodName())) {
|
||||
logger($controllerClass . '::' . $routeContextData->getMethodName());
|
||||
|
||||
$response->code(404);
|
||||
$response->code(Code::NOT_FOUND->value);
|
||||
$response->append(self::OOPS_MESSAGE);
|
||||
|
||||
throw new RuntimeException(self::OOPS_MESSAGE);
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->context->setTrasientKey(self::CONTEXT_ACTION_NAME, $routeContextData->getActionName());
|
||||
@@ -109,10 +110,8 @@ final class Bootstrap extends BootstrapBase
|
||||
)
|
||||
);
|
||||
|
||||
$controller = $this->createObjectFor($controllerClass);
|
||||
|
||||
return call_user_func_array(
|
||||
[$controller, $routeContextData->getMethodName()],
|
||||
[$this->buildInstanceFor($controllerClass), $routeContextData->getMethodName()],
|
||||
$routeContextData->getMethodParams()
|
||||
);
|
||||
} catch (SessionTimeout) {
|
||||
@@ -125,7 +124,12 @@ final class Bootstrap extends BootstrapBase
|
||||
if (DEBUG) {
|
||||
echo $e->getTraceAsString();
|
||||
}
|
||||
|
||||
$response->code(Code::INTERNAL_SERVER_ERROR->value);
|
||||
$response->append($e->getMessage());
|
||||
}
|
||||
|
||||
return $response;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ use SP\Domain\Core\Exceptions\CheckException;
|
||||
use SP\Domain\Core\Exceptions\ConfigException;
|
||||
use SP\Domain\Core\Exceptions\InitializationException;
|
||||
use SP\Domain\Http\Ports\RequestService;
|
||||
use SP\Domain\Upgrade\Services\UpgradeConfig;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use Symfony\Component\Debug\Debug;
|
||||
use Throwable;
|
||||
|
||||
@@ -61,9 +59,9 @@ use function SP\processException;
|
||||
*/
|
||||
abstract class BootstrapBase implements BootstrapInterface
|
||||
{
|
||||
public const CONTEXT_ACTION_NAME = "_actionName";
|
||||
public const CONTEXT_ACTION_NAME = '_actionName';
|
||||
|
||||
protected const OOPS_MESSAGE = "Oops, it looks like this content does not exist...";
|
||||
protected const OOPS_MESSAGE = 'Oops, it looks like this content does not exist...';
|
||||
public static mixed $LOCK;
|
||||
public static bool $checkPhpVersion = false;
|
||||
|
||||
@@ -146,7 +144,6 @@ abstract class BootstrapBase implements BootstrapInterface
|
||||
/**
|
||||
* @throws CheckException
|
||||
* @throws ConfigException
|
||||
* @throws FileException
|
||||
* @throws InitializationException
|
||||
*/
|
||||
final protected function initializeCommon(): void
|
||||
@@ -239,23 +236,22 @@ abstract class BootstrapBase implements BootstrapInterface
|
||||
*/
|
||||
private function initConfig(): void
|
||||
{
|
||||
UpgradeConfig::needsUpgrade($this->configData->getConfigVersion());
|
||||
|
||||
ConfigUtil::checkConfigDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* FIXME: delete
|
||||
* @template T of object
|
||||
* @param class-string<T> $class
|
||||
* @return T&object
|
||||
*/
|
||||
final protected function createObjectFor(string $class): object
|
||||
final protected function buildInstanceFor(string $class): object
|
||||
{
|
||||
try {
|
||||
return $this->container->get($class);
|
||||
} catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
|
||||
processException($e);
|
||||
|
||||
throw new RuntimeException($e);
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
@@ -24,25 +22,19 @@ declare(strict_types=1);
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Domain\Upgrade\Services;
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SP\Domain\Common\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* Class UpgradeApp
|
||||
* Class UpgradeHandler
|
||||
*/
|
||||
final class UpgradeApp extends UpgradeBase
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final class UpgradeVersion
|
||||
{
|
||||
protected static function getUpgrades(): array
|
||||
public function __construct(public readonly string $version)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function applyUpgrade(string $version): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function commitVersion(string $version): void
|
||||
{
|
||||
$this->configData->setAppVersion($version);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* sysPass
|
||||
@@ -25,6 +26,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace SP\Domain\Common\Models;
|
||||
|
||||
use SP\Domain\Core\Exceptions\SPException;
|
||||
|
||||
/**
|
||||
* Interface HydratableModel
|
||||
*/
|
||||
@@ -33,18 +36,20 @@ interface HydratableModel
|
||||
/**
|
||||
* Deserialize the hydratable property and returns the object.
|
||||
*
|
||||
* @template T
|
||||
* @param class-string<T> $class
|
||||
* @template THydrate
|
||||
* @param class-string<THydrate> $class
|
||||
*
|
||||
* @return T|null
|
||||
* @return THydrate|null
|
||||
* @throws SPException
|
||||
*/
|
||||
public function hydrate(string $class): ?object;
|
||||
|
||||
/**
|
||||
* Serialize the object in the hydratable property
|
||||
* @param object $object
|
||||
*
|
||||
* @return static A new instance of the model with the serialized property
|
||||
* @param object $object
|
||||
* @return static|null A new instance of the model with the serialized property or null if the property
|
||||
* couldn't be serialized
|
||||
*/
|
||||
public function dehydrate(object $object): static;
|
||||
public function dehydrate(object $object): static|null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* sysPass
|
||||
@@ -28,7 +29,6 @@ namespace SP\Domain\Common\Models;
|
||||
use ReflectionClass;
|
||||
use SP\Domain\Common\Adapters\Serde;
|
||||
use SP\Domain\Common\Attributes\Hydratable;
|
||||
use SP\Domain\Core\Exceptions\SPException;
|
||||
|
||||
/**
|
||||
* Trait SerializedModel
|
||||
@@ -36,30 +36,34 @@ use SP\Domain\Core\Exceptions\SPException;
|
||||
trait SerializedModel
|
||||
{
|
||||
/**
|
||||
* @template THydrate
|
||||
* @param class-string<THydrate> $class
|
||||
*
|
||||
* @return THydrate|null
|
||||
* @throws SPException
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hydrate(string $class): ?object
|
||||
{
|
||||
return $this->parseAttribute(
|
||||
function (Hydratable $hydratable) use ($class) {
|
||||
$valid = array_filter(
|
||||
$hydratable->getTargetClass(),
|
||||
static fn(string $targetClass) => is_a($class, $targetClass, true)
|
||||
);
|
||||
|
||||
$property = $this->{$hydratable->getSourceProperty()};
|
||||
|
||||
if (count($valid) > 0 && $property !== null) {
|
||||
return Serde::deserialize($property, $class) ?: null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function parseAttribute(callable $callback): mixed
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($this);
|
||||
|
||||
foreach ($reflectionClass->getAttributes(Hydratable::class) as $attribute) {
|
||||
/** @var Hydratable $instance */
|
||||
$instance = $attribute->newInstance();
|
||||
|
||||
$valid = array_filter(
|
||||
$instance->getTargetClass(),
|
||||
static fn(string $targetClass) => is_a($class, $targetClass, true)
|
||||
);
|
||||
|
||||
$property = $this->{$instance->getSourceProperty()};
|
||||
|
||||
if (count($valid) > 0 && $property !== null) {
|
||||
return Serde::deserialize($property, $class) ?: null;
|
||||
}
|
||||
return $callback($attribute->newInstance());
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -68,24 +72,21 @@ trait SerializedModel
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dehydrate(object $object): static
|
||||
public function dehydrate(object $object): static|null
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($this);
|
||||
return $this->parseAttribute(
|
||||
function (Hydratable $hydratable) use ($object) {
|
||||
$valid = array_filter(
|
||||
$hydratable->getTargetClass(),
|
||||
static fn(string $targetClass) => is_a($object, $targetClass)
|
||||
);
|
||||
|
||||
foreach ($reflectionClass->getAttributes(Hydratable::class) as $attribute) {
|
||||
/** @var Hydratable $instance */
|
||||
$instance = $attribute->newInstance();
|
||||
if (count($valid) > 0) {
|
||||
return $this->mutate([$hydratable->getSourceProperty() => Serde::serialize($object)]);
|
||||
}
|
||||
|
||||
$valid = array_filter(
|
||||
$instance->getTargetClass(),
|
||||
static fn(string $targetClass) => is_a($object, $targetClass, true)
|
||||
);
|
||||
|
||||
if (count($valid) > 0) {
|
||||
return $this->mutate([$instance->getSourceProperty() => Serde::serialize($object)]);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
44
lib/SP/Domain/Http/Code.php
Normal file
44
lib/SP/Domain/Http/Code.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Domain\Http;
|
||||
|
||||
/**
|
||||
* Enum Code
|
||||
*/
|
||||
enum Code: int
|
||||
{
|
||||
case INTERNAL_SERVER_ERROR = 500;
|
||||
case SERVICE_UNAVALIABLE = 503;
|
||||
case BAD_REQUEST = 400;
|
||||
case UNAUTHORIZED = 401;
|
||||
case FORBIDDEN = 403;
|
||||
case NOT_FOUND = 404;
|
||||
case OK = 200;
|
||||
case CREATED = 201;
|
||||
case NO_CONTENT = 204;
|
||||
case MOVED_PERMANENTLY = 301;
|
||||
case FOUND = 302;
|
||||
case NOT_MODIFIED = 304;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
|
||||
37
lib/SP/Domain/Upgrade/Ports/UpgradeHandlerService.php
Normal file
37
lib/SP/Domain/Upgrade/Ports/UpgradeHandlerService.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SP\Domain\Upgrade\Ports;
|
||||
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
|
||||
/**
|
||||
* Interface UpgradeHandler
|
||||
*/
|
||||
interface UpgradeHandlerService
|
||||
{
|
||||
public function apply(string $version, ConfigDataInterface $configData): bool;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
@@ -32,13 +33,16 @@ use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
*/
|
||||
interface UpgradeService
|
||||
{
|
||||
/**
|
||||
* Check if it needs to be upgraded
|
||||
*/
|
||||
public static function needsUpgrade(string $version): bool;
|
||||
|
||||
/**
|
||||
* Performs the upgrading process
|
||||
*/
|
||||
public function upgrade(string $version, ConfigDataInterface $configData);
|
||||
|
||||
/**
|
||||
* Register an upgrade class to run upgrade tasks.
|
||||
*
|
||||
* @param string $class
|
||||
* @return void
|
||||
*/
|
||||
public function registerUpgradeHandler(string $class): void;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
@@ -25,28 +26,44 @@ declare(strict_types=1);
|
||||
|
||||
namespace SP\Domain\Upgrade\Services;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionClass;
|
||||
use SP\Core\Application;
|
||||
use SP\Core\Events\Event;
|
||||
use SP\Core\Events\EventMessage;
|
||||
use SP\Domain\Common\Attributes\UpgradeVersion;
|
||||
use SP\Domain\Common\Providers\Version;
|
||||
use SP\Domain\Common\Services\Service;
|
||||
use SP\Domain\Common\Services\ServiceException;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Core\Exceptions\InvalidClassException;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Ports\UpgradeHandlerService;
|
||||
use SP\Domain\Upgrade\Ports\UpgradeService;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use Throwable;
|
||||
|
||||
use function SP\__u;
|
||||
use function SP\logger;
|
||||
|
||||
/**
|
||||
* Class UpgradeBase
|
||||
* Class Upgrade
|
||||
*/
|
||||
abstract class UpgradeBase extends Service implements UpgradeService
|
||||
final class Upgrade extends Service implements UpgradeService
|
||||
{
|
||||
protected ?ConfigDataInterface $configData = null;
|
||||
|
||||
public function __construct(Application $application, FileHandlerProvider $fileHandlerProvider)
|
||||
{
|
||||
/**
|
||||
* @var array<string> $upgradeHandlers
|
||||
*/
|
||||
private array $upgradeHandlers = [];
|
||||
|
||||
public function __construct(
|
||||
Application $application,
|
||||
FileHandlerProvider $fileHandlerProvider,
|
||||
private readonly ContainerInterface $container
|
||||
) {
|
||||
parent::__construct($application);
|
||||
|
||||
$this->eventDispatcher->attach($fileHandlerProvider);
|
||||
@@ -54,20 +71,11 @@ abstract class UpgradeBase extends Service implements UpgradeService
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
final public static function needsUpgrade(string $version): bool
|
||||
{
|
||||
return !empty($version) && Version::checkVersion($version, static::getUpgrades());
|
||||
}
|
||||
|
||||
abstract protected static function getUpgrades(): array;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws UpgradeException
|
||||
* @throws FileException
|
||||
* @throws ServiceException
|
||||
* @throws UpgradeException
|
||||
*/
|
||||
final public function upgrade(string $version, ConfigDataInterface $configData): void
|
||||
public function upgrade(string $version, ConfigDataInterface $configData): void
|
||||
{
|
||||
$this->configData = $configData;
|
||||
|
||||
@@ -81,22 +89,15 @@ abstract class UpgradeBase extends Service implements UpgradeService
|
||||
)
|
||||
);
|
||||
|
||||
$upgradeVersions = array_filter(
|
||||
static::getUpgrades(),
|
||||
static fn(string $appVersion) => Version::checkVersion($version, $appVersion)
|
||||
);
|
||||
|
||||
foreach ($upgradeVersions as $upgradeVersion) {
|
||||
if ($this->applyUpgrade($upgradeVersion) === false) {
|
||||
foreach ($this->getTargetUpgradeHandlers($version) as $upgradeHandler) {
|
||||
if (!$upgradeHandler->apply($version, $configData)) {
|
||||
throw UpgradeException::critical(
|
||||
__u('Error while applying the update'),
|
||||
__u('Please, check the event log for more details')
|
||||
);
|
||||
}
|
||||
|
||||
logger('Upgrade: ' . $upgradeVersion);
|
||||
|
||||
$this->commitVersion($upgradeVersion);
|
||||
logger('Upgrade: ' . $upgradeHandler::class);
|
||||
|
||||
$this->config->save($configData, false);
|
||||
}
|
||||
@@ -110,7 +111,45 @@ abstract class UpgradeBase extends Service implements UpgradeService
|
||||
);
|
||||
}
|
||||
|
||||
abstract protected function applyUpgrade(string $version): bool;
|
||||
/**
|
||||
* @return iterable<UpgradeHandlerService>
|
||||
* @throws ServiceException
|
||||
*/
|
||||
private function getTargetUpgradeHandlers(string $version): iterable
|
||||
{
|
||||
try {
|
||||
foreach ($this->upgradeHandlers as $class) {
|
||||
$reflection = new ReflectionClass($class);
|
||||
/** @var ReflectionAttribute<UpgradeVersion> $attribute */
|
||||
foreach ($reflection->getAttributes(UpgradeVersion::class) as $attribute) {
|
||||
$instance = $attribute->newInstance();
|
||||
|
||||
abstract protected function commitVersion(string $version): void;
|
||||
if (Version::checkVersion($version, $instance->version)) {
|
||||
yield $this->container->get($class);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
throw ServiceException::from($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServiceException
|
||||
* @throws InvalidClassException
|
||||
*/
|
||||
public function registerUpgradeHandler(string $class): void
|
||||
{
|
||||
if (!class_exists($class) || !is_subclass_of($class, UpgradeHandlerService::class)) {
|
||||
throw InvalidClassException::error('Class does not either exist or implement UpgradeService class');
|
||||
}
|
||||
|
||||
$hash = sha1($class);
|
||||
|
||||
if (array_key_exists($hash, $this->upgradeHandlers)) {
|
||||
throw ServiceException::error('Class already registered');
|
||||
}
|
||||
|
||||
$this->upgradeHandlers[$hash] = $class;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
@@ -30,10 +30,12 @@ use Exception;
|
||||
use SP\Core\Application;
|
||||
use SP\Core\Events\Event;
|
||||
use SP\Core\Events\EventMessage;
|
||||
use SP\Domain\Common\Attributes\UpgradeVersion;
|
||||
use SP\Domain\Common\Services\Service;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Database\Ports\DatabaseInterface;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Ports\UpgradeHandlerService;
|
||||
use SP\Infrastructure\Database\MysqlFileParser;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Infrastructure\File\FileHandler;
|
||||
use SP\Infrastructure\File\FileSystem;
|
||||
|
||||
@@ -45,44 +47,32 @@ use function SP\processException;
|
||||
/**
|
||||
* Class UpgradeDatabase
|
||||
*/
|
||||
final class UpgradeDatabase extends UpgradeBase
|
||||
#[UpgradeVersion('400.24210101')]
|
||||
final class UpgradeDatabase extends Service implements UpgradeHandlerService
|
||||
{
|
||||
public function __construct(
|
||||
Application $application,
|
||||
FileHandlerProvider $fileHandlerProvider,
|
||||
private readonly DatabaseInterface $database,
|
||||
) {
|
||||
parent::__construct($application, $fileHandlerProvider);
|
||||
}
|
||||
|
||||
protected static function getUpgrades(): array
|
||||
{
|
||||
return [
|
||||
'400.24210101',
|
||||
];
|
||||
}
|
||||
|
||||
protected function commitVersion(string $version): void
|
||||
{
|
||||
$this->configData->setDatabaseVersion($version);
|
||||
parent::__construct($application);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UpgradeException
|
||||
*/
|
||||
protected function applyUpgrade(string $version): bool
|
||||
public function apply(string $version, ConfigDataInterface $configData): bool
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
foreach ($this->getQueriesFromFile($version) as $query) {
|
||||
$count++;
|
||||
|
||||
try {
|
||||
$this->eventDispatcher->notify(
|
||||
'upgrade.db.process',
|
||||
new Event($this, EventMessage::factory()->addDetail(__u('Version'), $version))
|
||||
);
|
||||
$this->eventDispatcher->notify(
|
||||
'upgrade.db.process',
|
||||
new Event($this, EventMessage::factory()->addDetail(__u('Version'), $version))
|
||||
);
|
||||
|
||||
try {
|
||||
$this->database->runQueryRaw($query);
|
||||
} catch (Exception $e) {
|
||||
processException($e);
|
||||
@@ -109,6 +99,8 @@ final class UpgradeDatabase extends UpgradeBase
|
||||
throw UpgradeException::error(__u('Update file does not contain data'), $version);
|
||||
}
|
||||
|
||||
$configData->setDatabaseVersion($version);
|
||||
|
||||
$this->eventDispatcher->notify(
|
||||
'upgrade.db.process',
|
||||
new Event(
|
||||
@@ -123,13 +115,13 @@ final class UpgradeDatabase extends UpgradeBase
|
||||
/**
|
||||
* @throws UpgradeException
|
||||
*/
|
||||
private function getQueriesFromFile(string $filename): iterable
|
||||
private function getQueriesFromFile(string $version): iterable
|
||||
{
|
||||
$fileName = FileSystem::buildPath(SQL_PATH, str_replace('.', '', $filename) . '.sql');
|
||||
$filename = FileSystem::buildPath(SQL_PATH, str_replace('.', '', $version) . '.sql');
|
||||
|
||||
try {
|
||||
return (new MysqlFileParser(new FileHandler($fileName)))->parse('$$');
|
||||
} catch (FileException $e) {
|
||||
return (new MysqlFileParser(new FileHandler($filename)))->parse('$$');
|
||||
} catch (Exception $e) {
|
||||
processException($e);
|
||||
|
||||
throw UpgradeException::error($e->getMessage());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* sysPass
|
||||
@@ -88,7 +89,7 @@ class FileSystem
|
||||
* @param string $file
|
||||
* @param class-string<T>|null $class
|
||||
*
|
||||
* @return null|T
|
||||
* @return mixed|T
|
||||
* @throws FileException
|
||||
* @throws InvalidClassException
|
||||
*/
|
||||
@@ -98,13 +99,13 @@ class FileSystem
|
||||
$out = require $file;
|
||||
|
||||
if ($class && class_exists($class) && !$out instanceof $class) {
|
||||
throw new InvalidClassException(__u('Invalid class for loaded file data'));
|
||||
throw InvalidClassException::error(__u('Invalid class for loaded file data'));
|
||||
}
|
||||
|
||||
return $out;
|
||||
} else {
|
||||
throw new FileException(sprintf(__('File not found: %s'), $file));
|
||||
}
|
||||
|
||||
throw FileException::error(sprintf(__('File not found: %s'), $file));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace SP\Tests\Domain\Config\Services;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\Exception;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use SP\Core\Application;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Config\Ports\ConfigFileService;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Services\UpgradeConfig;
|
||||
use SP\Domain\Upgrade\Services\UpgradeException;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Tests\UnitaryTestCase;
|
||||
|
||||
/**
|
||||
* Class UpgradeConfigTest
|
||||
*
|
||||
*/
|
||||
#[Group('unitary')]
|
||||
class UpgradeConfigTest extends UnitaryTestCase
|
||||
{
|
||||
private FileHandlerProvider|MockObject $fileLogHandlerProvider;
|
||||
|
||||
public static function versionDataProvider(): array
|
||||
{
|
||||
return [
|
||||
['320.20062801', false],
|
||||
['340.00000000', false]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws FileException
|
||||
* @throws UpgradeException
|
||||
*/
|
||||
public function testUpgrade()
|
||||
{
|
||||
$version = '200.00000000';
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
$configFileService = $this->createMock(ConfigFileService::class);
|
||||
$application = new Application(
|
||||
$configFileService,
|
||||
$this->application->getEventDispatcher(),
|
||||
$this->application->getContext()
|
||||
);
|
||||
|
||||
$configData->expects(self::never())
|
||||
->method('setConfigVersion')
|
||||
->with(self::anything());
|
||||
|
||||
$configFileService->expects(self::never())
|
||||
->method('save')
|
||||
->with($configData, false);
|
||||
|
||||
$upgradeConfig = new UpgradeConfig($application, $this->fileLogHandlerProvider);
|
||||
$upgradeConfig->upgrade($version, $configData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
#[DataProvider('versionDataProvider')]
|
||||
public function testNeedsUpgrade(string $version, bool $expected)
|
||||
{
|
||||
$this->assertEquals($expected, UpgradeConfig::needsUpgrade($version));
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->fileLogHandlerProvider = $this->createMock(FileHandlerProvider::class);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link http://syspass.org
|
||||
* @copyright 2012-2024 Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SP\Tests\Domain\Upgrade\Services;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\Exception;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Services\UpgradeApp;
|
||||
use SP\Domain\Upgrade\Services\UpgradeException;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Tests\UnitaryTestCase;
|
||||
|
||||
/**
|
||||
* Class UpgradeAppTest
|
||||
*/
|
||||
#[Group('unitary')]
|
||||
class UpgradeAppTest extends UnitaryTestCase
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws UpgradeException
|
||||
* @throws FileException
|
||||
*/
|
||||
public function testUpgrade()
|
||||
{
|
||||
$fileHandlerProvider = $this->createMock(FileHandlerProvider::class);
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
|
||||
$configData->expects($this->never())
|
||||
->method('setAppVersion');
|
||||
|
||||
$this->config->expects($this->never())
|
||||
->method('save');
|
||||
|
||||
$upgradeApp = new UpgradeApp($this->application, $fileHandlerProvider);
|
||||
$upgradeApp->upgrade('123', $configData);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link http://syspass.org
|
||||
* @copyright 2012-2024 Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace SP\Tests\Domain\Upgrade\Services;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\Exception;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Services\UpgradeConfig;
|
||||
use SP\Domain\Upgrade\Services\UpgradeException;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Tests\UnitaryTestCase;
|
||||
|
||||
/**
|
||||
* Class UpgradeConfigTest
|
||||
*/
|
||||
#[Group('unitary')]
|
||||
class UpgradeConfigTest extends UnitaryTestCase
|
||||
{
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws UpgradeException
|
||||
* @throws FileException
|
||||
*/
|
||||
public function testUpgrade()
|
||||
{
|
||||
$fileHandlerProvider = $this->createMock(FileHandlerProvider::class);
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
|
||||
$configData->expects($this->never())
|
||||
->method('setConfigVersion');
|
||||
|
||||
$this->config->expects($this->never())
|
||||
->method('save');
|
||||
|
||||
$upgradeConfig = new UpgradeConfig($this->application, $fileHandlerProvider);
|
||||
$upgradeConfig->upgrade('123', $configData);
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,12 @@ namespace SP\Tests\Domain\Upgrade\Services;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\Exception;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use RuntimeException;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Database\Ports\DatabaseInterface;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Services\UpgradeDatabase;
|
||||
use SP\Domain\Upgrade\Services\UpgradeException;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Tests\UnitaryTestCase;
|
||||
|
||||
/**
|
||||
@@ -43,83 +42,80 @@ use SP\Tests\UnitaryTestCase;
|
||||
#[Group('unitary')]
|
||||
class UpgradeDatabaseTest extends UnitaryTestCase
|
||||
{
|
||||
private UpgradeDatabase $upgradeDatabase;
|
||||
private DatabaseInterface|MockObject $database;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws UpgradeException
|
||||
* @throws FileException
|
||||
*/
|
||||
public function testUpgrade()
|
||||
{
|
||||
$fileHandlerProvider = $this->createMock(FileHandlerProvider::class);
|
||||
$database = $this->createMock(DatabaseInterface::class);
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
|
||||
$database->expects($this->exactly(2))
|
||||
->method('runQueryRaw')
|
||||
->with(
|
||||
...
|
||||
self::withConsecutive(
|
||||
['alter table CustomFieldData drop column id'],
|
||||
['alter table CustomFieldData add primary key (moduleId, itemId, definitionId)']
|
||||
)
|
||||
);
|
||||
$this->database->expects($this->exactly(2))
|
||||
->method('runQueryRaw')
|
||||
->with(
|
||||
...
|
||||
self::withConsecutive(
|
||||
['alter table CustomFieldData drop column id'],
|
||||
['alter table CustomFieldData add primary key (moduleId, itemId, definitionId)']
|
||||
)
|
||||
);
|
||||
|
||||
$configData->expects($this->once())
|
||||
->method('setDatabaseVersion')
|
||||
->with('400.24210101');
|
||||
|
||||
$this->config->expects($this->once())
|
||||
->method('save')
|
||||
->with($configData);
|
||||
|
||||
$upgradeDatabase = new UpgradeDatabase($this->application, $fileHandlerProvider, $database);
|
||||
$upgradeDatabase->upgrade('400.00000000', $configData);
|
||||
$this->upgradeDatabase->apply('400.24210101', $configData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws UpgradeException
|
||||
* @throws FileException
|
||||
*/
|
||||
public function testUpgradeWithException()
|
||||
{
|
||||
$fileHandlerProvider = $this->createMock(FileHandlerProvider::class);
|
||||
$database = $this->createMock(DatabaseInterface::class);
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
|
||||
$database->expects($this->once())
|
||||
->method('runQueryRaw')
|
||||
->willThrowException(new RuntimeException('test'));
|
||||
$this->database->expects($this->once())
|
||||
->method('runQueryRaw')
|
||||
->willThrowException(new RuntimeException('test'));
|
||||
|
||||
$configData->expects($this->never())
|
||||
->method('setDatabaseVersion');
|
||||
|
||||
$upgradeDatabase = new UpgradeDatabase($this->application, $fileHandlerProvider, $database);
|
||||
|
||||
$this->expectException(UpgradeException::class);
|
||||
$this->expectExceptionMessage('Error while updating the database');
|
||||
|
||||
$upgradeDatabase->upgrade('400.00000000', $configData);
|
||||
$this->upgradeDatabase->apply('400.24210101', $configData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws UpgradeException
|
||||
* @throws FileException
|
||||
*/
|
||||
public function testUpgradeWithNoUpgrades()
|
||||
public function testUpgradeWithFileException()
|
||||
{
|
||||
$fileHandlerProvider = $this->createMock(FileHandlerProvider::class);
|
||||
$database = $this->createMock(DatabaseInterface::class);
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
|
||||
$database->expects($this->never())
|
||||
->method('runQueryRaw');
|
||||
$this->database->expects($this->never())
|
||||
->method('runQueryRaw');
|
||||
|
||||
$configData->expects($this->never())
|
||||
->method('setDatabaseVersion');
|
||||
|
||||
$upgradeDatabase = new UpgradeDatabase($this->application, $fileHandlerProvider, $database);
|
||||
$upgradeDatabase->upgrade('400.24210101', $configData);
|
||||
$this->expectException(UpgradeException::class);
|
||||
$this->expectExceptionMessage('Failed to open stream: No such file or directory');
|
||||
|
||||
$this->upgradeDatabase->apply('400.00000000', $configData);
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->database = $this->createMock(DatabaseInterface::class);
|
||||
$this->upgradeDatabase = new UpgradeDatabase($this->application, $this->database);
|
||||
}
|
||||
}
|
||||
|
||||
217
tests/SP/Domain/Upgrade/Services/UpgradeTest.php
Normal file
217
tests/SP/Domain/Upgrade/Services/UpgradeTest.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
* @link https://syspass.org
|
||||
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
|
||||
*
|
||||
* This file is part of sysPass.
|
||||
*
|
||||
* sysPass is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* sysPass is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SP\Tests\Domain\Upgrade\Services;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\MockObject\Exception;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use RuntimeException;
|
||||
use SP\Domain\Common\Services\ServiceException;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Core\Exceptions\InvalidClassException;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Ports\UpgradeHandlerService;
|
||||
use SP\Domain\Upgrade\Services\Upgrade;
|
||||
use SP\Domain\Upgrade\Services\UpgradeException;
|
||||
use SP\Infrastructure\File\FileException;
|
||||
use SP\Tests\Stubs\UpgradeHandlerStub;
|
||||
use SP\Tests\UnitaryTestCase;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class UpgradeTest
|
||||
*/
|
||||
#[Group('unitary')]
|
||||
class UpgradeTest extends UnitaryTestCase
|
||||
{
|
||||
|
||||
private MockObject|ContainerInterface $container;
|
||||
private Upgrade $upgrade;
|
||||
|
||||
/**
|
||||
* @throws ServiceException
|
||||
* @throws InvalidClassException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testRegisterUpgradeHandler()
|
||||
{
|
||||
$handler = $this->createMock(UpgradeHandlerService::class);
|
||||
|
||||
$this->upgrade->registerUpgradeHandler($handler::class);
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServiceException
|
||||
* @throws InvalidClassException
|
||||
*/
|
||||
public function testRegisterUpgradeHandlerWithClassException()
|
||||
{
|
||||
$this->expectException(InvalidClassException::class);
|
||||
$this->expectExceptionMessage('Class does not either exist or implement UpgradeService class');
|
||||
|
||||
$this->upgrade->registerUpgradeHandler(stdClass::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ServiceException
|
||||
* @throws InvalidClassException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testRegisterUpgradeHandlerWithDuplicate()
|
||||
{
|
||||
$handler = $this->createMock(UpgradeHandlerService::class);
|
||||
|
||||
$this->upgrade->registerUpgradeHandler($handler::class);
|
||||
|
||||
$this->expectException(ServiceException::class);
|
||||
$this->expectExceptionMessage('Class already registered');
|
||||
|
||||
$this->upgrade->registerUpgradeHandler($handler::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws ServiceException
|
||||
* @throws FileException
|
||||
* @throws UpgradeException
|
||||
*/
|
||||
public function testUpgrade()
|
||||
{
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
$this->config->expects($this->never())
|
||||
->method('save');
|
||||
|
||||
$this->upgrade->upgrade('400.00000000', $configData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws ServiceException
|
||||
* @throws FileException
|
||||
* @throws UpgradeException
|
||||
* @throws InvalidClassException
|
||||
*/
|
||||
public function testUpgradeWithHandler()
|
||||
{
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
$handler = $this->createMock(UpgradeHandlerService::class);
|
||||
$handler->expects($this->exactly(2))
|
||||
->method('apply')
|
||||
->with('400.00000000', $configData)
|
||||
->willReturn(true);
|
||||
|
||||
$this->container
|
||||
->expects($this->exactly(2))
|
||||
->method('get')
|
||||
->with(UpgradeHandlerStub::class)
|
||||
->willReturn($handler);
|
||||
|
||||
$this->config->expects($this->exactly(2))
|
||||
->method('save')
|
||||
->with($configData, false);
|
||||
|
||||
$this->upgrade->registerUpgradeHandler(UpgradeHandlerStub::class);
|
||||
$this->upgrade->upgrade('400.00000000', $configData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws ServiceException
|
||||
* @throws FileException
|
||||
* @throws UpgradeException
|
||||
* @throws InvalidClassException
|
||||
*/
|
||||
public function testUpgradeWithHandlerWithFailedApply()
|
||||
{
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
$handler = $this->createMock(UpgradeHandlerService::class);
|
||||
$handler->expects($this->once())
|
||||
->method('apply')
|
||||
->with('400.00000000', $configData)
|
||||
->willReturn(false);
|
||||
|
||||
$this->container
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with(UpgradeHandlerStub::class)
|
||||
->willReturn($handler);
|
||||
|
||||
$this->config->expects($this->never())
|
||||
->method('save');
|
||||
|
||||
$this->upgrade->registerUpgradeHandler(UpgradeHandlerStub::class);
|
||||
|
||||
$this->expectException(UpgradeException::class);
|
||||
$this->expectExceptionMessage('Error while applying the update');
|
||||
|
||||
$this->upgrade->upgrade('400.00000000', $configData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws ServiceException
|
||||
* @throws FileException
|
||||
* @throws UpgradeException
|
||||
* @throws InvalidClassException
|
||||
*/
|
||||
public function testUpgradeWithException()
|
||||
{
|
||||
$configData = $this->createMock(ConfigDataInterface::class);
|
||||
$handler = $this->createMock(UpgradeHandlerService::class);
|
||||
$handler->expects($this->never())
|
||||
->method('apply');
|
||||
|
||||
$this->container
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with(UpgradeHandlerStub::class)
|
||||
->willThrowException(new RuntimeException('test'));
|
||||
|
||||
$this->config->expects($this->never())
|
||||
->method('save');
|
||||
|
||||
$this->upgrade->registerUpgradeHandler(UpgradeHandlerStub::class);
|
||||
|
||||
$this->expectException(ServiceException::class);
|
||||
$this->expectExceptionMessage('test');
|
||||
|
||||
$this->upgrade->upgrade('400.00000000', $configData);
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$fileHandlerProvider = $this->createMock(FileHandlerProvider::class);
|
||||
$this->container = $this->createMock(ContainerInterface::class);
|
||||
|
||||
$this->upgrade = new Upgrade($this->application, $fileHandlerProvider, $this->container);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
/**
|
||||
* sysPass
|
||||
*
|
||||
* @author nuxsmin
|
||||
@@ -24,38 +24,21 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SP\Domain\Upgrade\Services;
|
||||
namespace SP\Tests\Stubs;
|
||||
|
||||
use SP\Core\Application;
|
||||
use SP\Domain\Log\Ports\FileHandlerProvider;
|
||||
use SP\Domain\Upgrade\Ports\UpgradeConfigService;
|
||||
use SP\Domain\Common\Attributes\UpgradeVersion;
|
||||
use SP\Domain\Config\Ports\ConfigDataInterface;
|
||||
use SP\Domain\Upgrade\Ports\UpgradeHandlerService;
|
||||
|
||||
/**
|
||||
* Class UpgradeConfig
|
||||
* Class UpgradeHandlerStub
|
||||
*/
|
||||
final class UpgradeConfig extends UpgradeBase implements UpgradeConfigService
|
||||
#[UpgradeVersion('400.00000002')]
|
||||
#[UpgradeVersion('400.00000001')]
|
||||
#[UpgradeVersion('400.00000000')]
|
||||
class UpgradeHandlerStub implements UpgradeHandlerService
|
||||
{
|
||||
public function __construct(
|
||||
Application $application,
|
||||
FileHandlerProvider $fileLogHandlerProvider
|
||||
) {
|
||||
parent::__construct($application, $fileLogHandlerProvider);
|
||||
|
||||
$this->eventDispatcher->attach($fileLogHandlerProvider);
|
||||
}
|
||||
|
||||
|
||||
protected static function getUpgrades(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function commitVersion(string $version): void
|
||||
{
|
||||
$this->configData->setConfigVersion($version);
|
||||
}
|
||||
|
||||
protected function applyUpgrade(string $version): bool
|
||||
public function apply(string $version, ConfigDataInterface $configData): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user