From f0e5be2c52589353674a88805212cf5bac2ce582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D?= Date: Thu, 19 May 2022 08:25:16 +0200 Subject: [PATCH] chore: Build mock test for `FileBackupService` class. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rubén D --- .../api/Controllers/ConfigController.php | 5 +- .../Controllers/ConfigBackupController.php | 5 +- .../Controllers/ConfigManagerController.php | 12 +- composer.json | 2 +- composer.lock | 21 +- lib/SP/Services/Backup/BackupFiles.php | 169 +++++++++++ lib/SP/Services/Backup/FileBackupService.php | 284 +++++++----------- lib/SP/Storage/File/ArchiveHandler.php | 52 ++-- tests/SP/BypassFinalHook.php | 4 + .../Cli/Commands/BackupCommandTest.php | 6 +- .../Services/Backup/FileBackupServiceTest.php | 75 +++-- tests/SP/UnitaryTestCase.php | 12 +- tests/phpunit.xml | 4 +- 13 files changed, 402 insertions(+), 249 deletions(-) create mode 100644 lib/SP/Services/Backup/BackupFiles.php diff --git a/app/modules/api/Controllers/ConfigController.php b/app/modules/api/Controllers/ConfigController.php index 8f5039c4..c6431051 100644 --- a/app/modules/api/Controllers/ConfigController.php +++ b/app/modules/api/Controllers/ConfigController.php @@ -31,6 +31,7 @@ use SP\Core\Events\EventMessage; use SP\Core\Exceptions\InvalidClassException; use SP\Modules\Api\Controllers\Help\ConfigHelp; use SP\Services\Api\ApiResponse; +use SP\Services\Backup\BackupFiles; use SP\Services\Backup\FileBackupService; use SP\Services\Export\XmlExportService; @@ -66,12 +67,12 @@ final class ConfigController extends ControllerBase $backupFiles = [ 'files' => [ - 'app' => FileBackupService::getAppBackupFilename( + 'app' => BackupFiles::getAppBackupFilename( $path, $backupService->getHash(), true ), - 'db' => FileBackupService::getDbBackupFilename( + 'db' => BackupFiles::getDbBackupFilename( $path, $backupService->getHash(), true diff --git a/app/modules/web/Controllers/ConfigBackupController.php b/app/modules/web/Controllers/ConfigBackupController.php index 6c0d3fdb..7cb67e6b 100644 --- a/app/modules/web/Controllers/ConfigBackupController.php +++ b/app/modules/web/Controllers/ConfigBackupController.php @@ -35,6 +35,7 @@ use SP\Core\Events\EventMessage; use SP\Core\Exceptions\SessionTimeout; use SP\Http\JsonResponse; use SP\Modules\Web\Controllers\Traits\ConfigTrait; +use SP\Services\Backup\BackupFiles; use SP\Services\Backup\FileBackupService; use SP\Services\Export\XmlExportService; use SP\Services\Export\XmlVerifyService; @@ -244,7 +245,7 @@ final class ConfigBackupController extends SimpleControllerBase try { SessionContext::close(); - $filePath = FileBackupService::getAppBackupFilename( + $filePath = BackupFiles::getAppBackupFilename( BACKUP_PATH, $this->configData->getBackupHash(), true @@ -299,7 +300,7 @@ final class ConfigBackupController extends SimpleControllerBase try { SessionContext::close(); - $filePath = FileBackupService::getDbBackupFilename( + $filePath = BackupFiles::getDbBackupFilename( BACKUP_PATH, $this->configData->getBackupHash(), true diff --git a/app/modules/web/Controllers/ConfigManagerController.php b/app/modules/web/Controllers/ConfigManagerController.php index ffa53c63..52f9617c 100644 --- a/app/modules/web/Controllers/ConfigManagerController.php +++ b/app/modules/web/Controllers/ConfigManagerController.php @@ -52,7 +52,7 @@ use SP\Providers\Mail\MailHandler; use SP\Repositories\NoSuchItemException; use SP\Services\Account\AccountService; use SP\Services\Auth\AuthException; -use SP\Services\Backup\FileBackupService; +use SP\Services\Backup\BackupFiles; use SP\Services\Config\ConfigService; use SP\Services\Crypt\TemporaryMasterPassService; use SP\Services\Export\XmlExportService; @@ -441,15 +441,17 @@ final class ConfigManagerController extends ControllerBase $template->assign('siteName', AppInfoInterface::APP_NAME); $backupAppFile = new FileHandler( - FileBackupService::getAppBackupFilename( + BackupFiles::getAppBackupFilename( BACKUP_PATH, - $this->configData->getBackupHash() ?: '', true + $this->configData->getBackupHash() ?: '', + true ) ); $backupDbFile = new FileHandler( - FileBackupService::getDbBackupFilename( + BackupFiles::getDbBackupFilename( BACKUP_PATH, - $this->configData->getBackupHash() ?: '', true + $this->configData->getBackupHash() ?: '', + true ) ); $exportFile = new FileHandler( diff --git a/composer.json b/composer.json index 9cf1c9b5..08560522 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "fzaninotto/faker": "1.9.x-dev", "fabpot/goutte": "^v3.2", "nikic/php-parser": "^v4.1", - "dg/bypass-finals": "^v1.3.1" + "dg/bypass-finals": "^v1.3" }, "suggest": { "syspass/plugin-authenticator": "^v2.2", diff --git a/composer.lock b/composer.lock index ea811574..efbe95d6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9984e99c8be6c53846bc4e8077ea5a7a", + "content-hash": "c621fdf745779bfd4d70c03e177361c7", "packages": [ { "name": "ademarre/binary-to-text-php", @@ -1438,16 +1438,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e" + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/9e4b005daa20b0c161f3845040046dc9ddc1d74e", - "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", "shasum": "" }, "require": { @@ -1493,7 +1493,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-02-11T19:23:53+00:00" + "time": "2022-05-16T17:09:47+00:00" }, { "name": "league/fractal", @@ -4854,12 +4854,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "2ed308386e65cafaadd54723f5b57775374c2ae8" + "reference": "c491d086242983f784b8af91cbb9de43d3374971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/2ed308386e65cafaadd54723f5b57775374c2ae8", - "reference": "2ed308386e65cafaadd54723f5b57775374c2ae8", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/c491d086242983f784b8af91cbb9de43d3374971", + "reference": "c491d086242983f784b8af91cbb9de43d3374971", "shasum": "" }, "conflict": { @@ -4892,6 +4892,7 @@ "bolt/core": "<=4.2", "bottelet/flarepoint": "<2.2.1", "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", "buddypress/buddypress": "<7.2.1", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", "bytefury/crater": "<6.0.2", @@ -5336,7 +5337,7 @@ "type": "tidelift" } ], - "time": "2022-05-11T11:06:18+00:00" + "time": "2022-05-16T08:10:11+00:00" }, { "name": "sebastian/cli-parser", diff --git a/lib/SP/Services/Backup/BackupFiles.php b/lib/SP/Services/Backup/BackupFiles.php new file mode 100644 index 00000000..58c707cd --- /dev/null +++ b/lib/SP/Services/Backup/BackupFiles.php @@ -0,0 +1,169 @@ +. + */ + +namespace SP\Services\Backup; + + +use SP\Core\AppInfoInterface; +use SP\Core\Exceptions\CheckException; +use SP\Core\PhpExtensionChecker; +use SP\Storage\File\ArchiveHandler; +use SP\Storage\File\FileHandler; + +/** + * BackupFiles + */ +final class BackupFiles +{ + private const BACKUP_PREFFIX = 'sysPassBackup'; + private string $hash; + private string $path; + private string $appBackupFilename; + private string $dbBackupFilename; + /** + * @var \SP\Core\PhpExtensionChecker + */ + private PhpExtensionChecker $extensionChecker; + + /** + * @param string $path The path where to store the backup files + * + * @throws \SP\Core\Exceptions\CheckException + */ + public function __construct(PhpExtensionChecker $extensionChecker, string $path = BACKUP_PATH) + { + $this->extensionChecker = $extensionChecker; + $this->path = $path; + $this->hash = $this->getBackupHash(); + + $this->checkBackupDir(); + + $this->appBackupFilename = self::getAppBackupFilename($this->path, $this->hash); + $this->dbBackupFilename = self::getDbBackupFilename($this->path, $this->hash); + } + + /** + * Generate a unique hash to avoid unwated downloads + * + * @return string + */ + private function getBackupHash(): string + { + return sha1(uniqid(self::BACKUP_PREFFIX, true)); + } + + /** + * Check and create the backup dir + * + * @throws CheckException + */ + private function checkBackupDir(): void + { + if (is_dir($this->path) === false + && !@mkdir($concurrentDirectory = $this->path, 0750, true) + && !is_dir($concurrentDirectory) + ) { + throw new CheckException( + sprintf(__('Unable to create the backups directory ("%s")'), $this->path) + ); + } + + if (!is_writable($this->path)) { + throw new CheckException( + __u('Please, check the backup directory permissions') + ); + } + + } + + public static function getAppBackupFilename( + string $path, + string $hash, + bool $compressed = false + ): string { + $file = $path.DIRECTORY_SEPARATOR.AppInfoInterface::APP_NAME.'_app-'.$hash; + + if ($compressed) { + return $file.ArchiveHandler::COMPRESS_EXTENSION; + } + + return $file; + } + + public static function getDbBackupFilename( + string $path, + string $hash, + bool $compressed = false + ): string { + $file = $path.DIRECTORY_SEPARATOR.AppInfoInterface::APP_NAME.'_db-'.$hash; + + if ($compressed) { + return $file.ArchiveHandler::COMPRESS_EXTENSION; + } + + return $file.'.sql'; + } + + /** + * @return \SP\Storage\File\FileHandler + */ + public function getAppBackupFileHandler(): FileHandler + { + return new FileHandler($this->appBackupFilename); + } + + /** + * @return \SP\Storage\File\FileHandler + */ + public function getDbBackupFileHandler(): FileHandler + { + return new FileHandler($this->dbBackupFilename); + } + + /** + * @return \SP\Storage\File\ArchiveHandler + * @throws \SP\Core\Exceptions\CheckException + */ + public function getDbBackupArchiveHandler(): ArchiveHandler + { + return new ArchiveHandler($this->dbBackupFilename, $this->extensionChecker); + } + + /** + * @return \SP\Storage\File\ArchiveHandler + * @throws \SP\Core\Exceptions\CheckException + */ + public function getAppBackupArchiveHandler(): ArchiveHandler + { + return new ArchiveHandler($this->appBackupFilename, $this->extensionChecker); + } + + /** + * @return string + */ + public function getHash(): string + { + return $this->hash; + } +} \ No newline at end of file diff --git a/lib/SP/Services/Backup/FileBackupService.php b/lib/SP/Services/Backup/FileBackupService.php index 926ab536..375204bd 100644 --- a/lib/SP/Services/Backup/FileBackupService.php +++ b/lib/SP/Services/Backup/FileBackupService.php @@ -26,16 +26,15 @@ namespace SP\Services\Backup; use Exception; use PDO; -use Psr\Container\ContainerExceptionInterface; -use Psr\Container\NotFoundExceptionInterface; +use SP\Config\Config; use SP\Config\ConfigDataInterface; use SP\Core\AppInfoInterface; +use SP\Core\Application; use SP\Core\Events\Event; +use SP\Core\Events\EventDispatcher; use SP\Core\Events\EventMessage; use SP\Core\Exceptions\CheckException; use SP\Core\Exceptions\SPException; -use SP\Core\PhpExtensionChecker; -use SP\Services\Service; use SP\Services\ServiceException; use SP\Storage\Database\Database; use SP\Storage\Database\DatabaseUtil; @@ -50,53 +49,65 @@ defined('APP_ROOT') || die(); /** * Esta clase es la encargada de realizar la copia de sysPass. */ -final class FileBackupService extends Service +final class FileBackupService { - private const BACKUP_INCLUDE_REGEX = /** @lang RegExp */ + public const BACKUP_INCLUDE_REGEX = /** @lang RegExp */ '#^(?:[A-Z]:)?(?:/(?!(\.git|backup|cache|temp|vendor|tests))[^/]+)+/[^/]+\.\w+$#Di'; - private ?ConfigDataInterface $configData = null; - private ?string $path = null; - private ?string $backupFileApp = null; - private ?string $backupFileDb = null; - private ?PhpExtensionChecker $extensionChecker = null; - private ?string $hash = null; + private Database $database; + private DatabaseUtil $databaseUtil; + private EventDispatcher $eventDispatcher; + private Config $config; + private ConfigDataInterface $configData; + private BackupFiles $backupFiles; + private ?string $backupPath = null; + + public function __construct( + Application $application, + Database $database, + DatabaseUtil $databaseUtil, + BackupFiles $backupFiles + ) { + $this->config = $application->getConfig(); + $this->eventDispatcher = $application->getEventDispatcher(); + $this->database = $database; + $this->databaseUtil = $databaseUtil; + $this->backupFiles = $backupFiles; + + $this->configData = $this->config->getConfigData(); + } /** * Realizar backup de la BBDD y aplicación. * * @throws ServiceException */ - public function doBackup(string $path): void + public function doBackup(string $backupPath = BACKUP_PATH, string $applicationPath = APP_ROOT): void { set_time_limit(0); - $this->path = $path; - - $this->checkBackupDir(); - - // Generar hash unico para evitar descargas no permitidas - $this->hash = sha1(uniqid('sysPassBackup', true)); - - $this->backupFileApp = self::getAppBackupFilename($path, $this->hash); - $this->backupFileDb = self::getDbBackupFilename($path, $this->hash); + $this->backupPath = $backupPath; try { $this->deleteOldBackups(); - $this->eventDispatcher->notifyEvent('run.backup.start', - new Event($this, - EventMessage::factory()->addDescription(__u('Make Backup')))); + $this->eventDispatcher->notifyEvent( + 'run.backup.start', + new Event( + $this, + EventMessage::factory()->addDescription(__u('Make Backup')) + ) + ); - $this->backupTables(new FileHandler($this->backupFileDb)); + $this->backupTables($this->backupFiles->getDbBackupFileHandler()); - if (!$this->backupApp() - && !$this->backupAppLegacyLinux() + if (!$this->backupApp($applicationPath) + && !$this->backupAppLegacyLinux($applicationPath) ) { throw new ServiceException(__u('Error while doing the backup in compatibility mode')); } - $this->configData->setBackupHash($this->hash); + $this->configData->setBackupHash($this->backupFiles->getHash()); $this->config->saveConfig($this->configData); } catch (ServiceException $e) { throw $e; @@ -113,73 +124,21 @@ final class FileBackupService extends Service } } - /** - * Comprobar y crear el directorio de backups. - * - * @throws ServiceException - */ - private function checkBackupDir(): void - { - if (is_dir($this->path) === false - && !@mkdir($concurrentDirectory = $this->path, 0750, true) - && !is_dir($concurrentDirectory) - ) { - throw new ServiceException( - sprintf(__('Unable to create the backups directory ("%s")'), $this->path)); - } - - if (!is_writable($this->path)) { - throw new ServiceException( - __u('Please, check the backup directory permissions')); - } - - } - - public static function getAppBackupFilename( - string $path, - string $hash, - bool $compressed = false - ): string - { - $file = $path . DIRECTORY_SEPARATOR . AppInfoInterface::APP_NAME . '_app-' . $hash; - - if ($compressed) { - return $file . ArchiveHandler::COMPRESS_EXTENSION; - } - - return $file; - } - - public static function getDbBackupFilename( - string $path, - string $hash, - bool $compressed = false - ): string - { - $file = $path . DIRECTORY_SEPARATOR . AppInfoInterface::APP_NAME . '_db-' . $hash; - - if ($compressed) { - return $file . ArchiveHandler::COMPRESS_EXTENSION; - } - - return $file . '.sql'; - } - /** * Eliminar las copias de seguridad anteriores */ private function deleteOldBackups(): void { - $path = $this->path . DIRECTORY_SEPARATOR . AppInfoInterface::APP_NAME; + $path = $this->backupPath.DIRECTORY_SEPARATOR.AppInfoInterface::APP_NAME; array_map( static function ($file) { return @unlink($file); }, array_merge( - glob($path . '_db-*'), - glob($path . '_app-*'), - glob($path . '*.sql') + glob($path.'_db-*'), + glob($path.'_app-*'), + glob($path.'*.sql') ) ); } @@ -196,71 +155,63 @@ final class FileBackupService extends Service */ private function backupTables( FileHandler $fileHandler, - string $tables = '*' - ): void - { - $this->eventDispatcher->notifyEvent('run.backup.process', - new Event($this, - EventMessage::factory()->addDescription(__u('Copying database'))) + ): void { + $this->eventDispatcher->notifyEvent( + 'run.backup.process', + new Event( + $this, + EventMessage::factory()->addDescription(__u('Copying database')) + ) ); $fileHandler->open('w'); - $db = $this->dic->get(Database::class); - $databaseUtil = $this->dic->get(DatabaseUtil::class); - $queryData = new QueryData(); - if ($tables === '*') { - $resTables = DatabaseUtil::TABLES; - } else { - $resTables = is_array($tables) - ? $tables - : explode(',', $tables); - } + $tables = DatabaseUtil::TABLES; - $lineSeparator = PHP_EOL . PHP_EOL; + $lineSeparator = PHP_EOL.PHP_EOL; - $dbname = $db->getDbHandler()->getDatabaseName(); + $dbname = $this->database->getDbHandler()->getDatabaseName(); - $sqlOut = '-- ' . PHP_EOL; - $sqlOut .= '-- sysPass DB dump generated on ' . time() . ' (START)' . PHP_EOL; - $sqlOut .= '-- ' . PHP_EOL; - $sqlOut .= '-- Please, do not alter this file, it could break your DB' . PHP_EOL; - $sqlOut .= '-- ' . PHP_EOL; - $sqlOut .= 'SET AUTOCOMMIT = 0;' . PHP_EOL; - $sqlOut .= 'SET FOREIGN_KEY_CHECKS = 0;' . PHP_EOL; - $sqlOut .= 'SET UNIQUE_CHECKS = 0;' . PHP_EOL; - $sqlOut .= '-- ' . PHP_EOL; - $sqlOut .= 'CREATE DATABASE IF NOT EXISTS `' . $dbname . '`;' . PHP_EOL . PHP_EOL; - $sqlOut .= 'USE `' . $dbname . '`;' . PHP_EOL . PHP_EOL; + $sqlOut = '-- '.PHP_EOL; + $sqlOut .= '-- sysPass DB dump generated on '.time().' (START)'.PHP_EOL; + $sqlOut .= '-- '.PHP_EOL; + $sqlOut .= '-- Please, do not alter this file, it could break your DB'.PHP_EOL; + $sqlOut .= '-- '.PHP_EOL; + $sqlOut .= 'SET AUTOCOMMIT = 0;'.PHP_EOL; + $sqlOut .= 'SET FOREIGN_KEY_CHECKS = 0;'.PHP_EOL; + $sqlOut .= 'SET UNIQUE_CHECKS = 0;'.PHP_EOL; + $sqlOut .= '-- '.PHP_EOL; + $sqlOut .= 'CREATE DATABASE IF NOT EXISTS `'.$dbname.'`;'.PHP_EOL.PHP_EOL; + $sqlOut .= 'USE `'.$dbname.'`;'.PHP_EOL.PHP_EOL; $fileHandler->write($sqlOut); $sqlOutViews = ''; // Recorrer las tablas y almacenar los datos - foreach ($resTables as $table) { - $tableName = is_object($table) ? $table->{'Tables_in_' . $dbname} : $table; + foreach ($tables as $table) { + $tableName = is_object($table) ? $table->{'Tables_in_'.$dbname} : $table; - $queryData->setQuery('SHOW CREATE TABLE ' . $tableName); + $queryData->setQuery('SHOW CREATE TABLE '.$tableName); // Consulta para crear la tabla - $txtCreate = $db->doQuery($queryData)->getData(); + $txtCreate = $this->database->doQuery($queryData)->getData(); if (isset($txtCreate->{'Create Table'})) { - $sqlOut = '-- ' . PHP_EOL; - $sqlOut .= '-- Table ' . strtoupper($tableName) . PHP_EOL; - $sqlOut .= '-- ' . PHP_EOL; - $sqlOut .= 'DROP TABLE IF EXISTS `' . $tableName . '`;' . PHP_EOL . PHP_EOL; - $sqlOut .= $txtCreate->{'Create Table'} . ';' . PHP_EOL . PHP_EOL; + $sqlOut = '-- '.PHP_EOL; + $sqlOut .= '-- Table '.strtoupper($tableName).PHP_EOL; + $sqlOut .= '-- '.PHP_EOL; + $sqlOut .= 'DROP TABLE IF EXISTS `'.$tableName.'`;'.PHP_EOL.PHP_EOL; + $sqlOut .= $txtCreate->{'Create Table'}.';'.PHP_EOL.PHP_EOL; $fileHandler->write($sqlOut); } elseif (isset($txtCreate->{'Create View'})) { - $sqlOutViews .= '-- ' . PHP_EOL; - $sqlOutViews .= '-- View ' . strtoupper($tableName) . PHP_EOL; - $sqlOutViews .= '-- ' . PHP_EOL; - $sqlOutViews .= 'DROP TABLE IF EXISTS `' . $tableName . '`;' . PHP_EOL . PHP_EOL; - $sqlOutViews .= $txtCreate->{'Create View'} . ';' . PHP_EOL . PHP_EOL; + $sqlOutViews .= '-- '.PHP_EOL; + $sqlOutViews .= '-- View '.strtoupper($tableName).PHP_EOL; + $sqlOutViews .= '-- '.PHP_EOL; + $sqlOutViews .= 'DROP TABLE IF EXISTS `'.$tableName.'`;'.PHP_EOL.PHP_EOL; + $sqlOutViews .= $txtCreate->{'Create View'}.';'.PHP_EOL.PHP_EOL; } $fileHandler->write($lineSeparator); @@ -270,28 +221,28 @@ final class FileBackupService extends Service $fileHandler->write($sqlOutViews); // Guardar los datos - foreach ($resTables as $tableName) { + foreach ($tables as $tableName) { // No guardar las vistas! if (strrpos($tableName, '_v') !== false) { continue; } - $queryData->setQuery('SELECT * FROM `' . $tableName . '`'); + $queryData->setQuery('SELECT * FROM `'.$tableName.'`'); // Consulta para obtener los registros de la tabla - $queryRes = $db->doQueryRaw($queryData, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL], false); + $queryRes = $this->database->doQueryRaw($queryData, [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL], false); $numColumns = $queryRes->columnCount(); while ($row = $queryRes->fetch(PDO::FETCH_NUM)) { - $fileHandler->write('INSERT INTO `' . $tableName . '` VALUES('); + $fileHandler->write('INSERT INTO `'.$tableName.'` VALUES('); $field = 1; foreach ($row as $value) { if (is_numeric($value)) { $fileHandler->write($value); } elseif ($value) { - $fileHandler->write($databaseUtil->escape($value)); + $fileHandler->write($this->databaseUtil->escape($value)); } else { $fileHandler->write(null); } @@ -303,25 +254,24 @@ final class FileBackupService extends Service $field++; } - $fileHandler->write(');' . PHP_EOL); + $fileHandler->write(');'.PHP_EOL); } } - $sqlOut = '-- ' . PHP_EOL; - $sqlOut .= 'SET AUTOCOMMIT = 1;' . PHP_EOL; - $sqlOut .= 'SET FOREIGN_KEY_CHECKS = 1;' . PHP_EOL; - $sqlOut .= 'SET UNIQUE_CHECKS = 1;' . PHP_EOL; - $sqlOut .= '-- ' . PHP_EOL; - $sqlOut .= '-- sysPass DB dump generated on ' . time() . ' (END)' . PHP_EOL; - $sqlOut .= '-- ' . PHP_EOL; - $sqlOut .= '-- Please, do not alter this file, it could break your DB' . PHP_EOL; - $sqlOut .= '-- ' . PHP_EOL . PHP_EOL; + $sqlOut = '-- '.PHP_EOL; + $sqlOut .= 'SET AUTOCOMMIT = 1;'.PHP_EOL; + $sqlOut .= 'SET FOREIGN_KEY_CHECKS = 1;'.PHP_EOL; + $sqlOut .= 'SET UNIQUE_CHECKS = 1;'.PHP_EOL; + $sqlOut .= '-- '.PHP_EOL; + $sqlOut .= '-- sysPass DB dump generated on '.time().' (END)'.PHP_EOL; + $sqlOut .= '-- '.PHP_EOL; + $sqlOut .= '-- Please, do not alter this file, it could break your DB'.PHP_EOL; + $sqlOut .= '-- '.PHP_EOL.PHP_EOL; $fileHandler->write($sqlOut); $fileHandler->close(); - $archive = new ArchiveHandler($fileHandler->getFile(), $this->extensionChecker); - $archive->compressFile($fileHandler->getFile()); + $this->backupFiles->getDbBackupArchiveHandler()->compressFile($fileHandler->getFile()); $fileHandler->delete(); } @@ -332,16 +282,17 @@ final class FileBackupService extends Service * @throws CheckException * @throws FileException */ - private function backupApp(): bool + private function backupApp(string $directory): bool { - $this->eventDispatcher->notifyEvent('run.backup.process', - new Event($this, EventMessage::factory() - ->addDescription(__u('Copying application'))) + $this->eventDispatcher->notifyEvent( + 'run.backup.process', + new Event( + $this, EventMessage::factory() + ->addDescription(__u('Copying application')) + ) ); - $archive = new ArchiveHandler($this->backupFileApp, $this->extensionChecker); - - $archive->compressDirectory(APP_ROOT, self::BACKUP_INCLUDE_REGEX); + $this->backupFiles->getAppBackupArchiveHandler()->compressDirectory($directory, self::BACKUP_INCLUDE_REGEX); return true; } @@ -351,7 +302,7 @@ final class FileBackupService extends Service * * @throws ServiceException */ - private function backupAppLegacyLinux(): int + private function backupAppLegacyLinux(string $directory): int { if (Checks::checkIsWindows()) { throw new ServiceException( @@ -360,17 +311,20 @@ final class FileBackupService extends Service ); } - $this->eventDispatcher->notifyEvent('run.backup.process', - new Event($this, EventMessage::factory() - ->addDescription(__u('Copying application'))) + $this->eventDispatcher->notifyEvent( + 'run.backup.process', + new Event( + $this, EventMessage::factory() + ->addDescription(__u('Copying application')) + ) ); $command = sprintf( 'tar czf %s%s %s --exclude "%s" 2>&1', - $this->backupFileApp, + $this->backupFiles->getAppBackupFileHandler()->getFile(), ArchiveHandler::COMPRESS_EXTENSION, - BASE_PATH, - $this->path + $directory, + $this->backupPath ); exec($command, $resOut, $resBakApp); @@ -379,16 +333,6 @@ final class FileBackupService extends Service public function getHash(): string { - return $this->hash; - } - - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - protected function initialize(): void - { - $this->configData = $this->config->getConfigData(); - $this->extensionChecker = $this->dic->get(PhpExtensionChecker::class); + return $this->backupFiles->getHash(); } } \ No newline at end of file diff --git a/lib/SP/Storage/File/ArchiveHandler.php b/lib/SP/Storage/File/ArchiveHandler.php index b29bbaec..60007232 100644 --- a/lib/SP/Storage/File/ArchiveHandler.php +++ b/lib/SP/Storage/File/ArchiveHandler.php @@ -27,7 +27,6 @@ namespace SP\Storage\File; use Phar; use PharData; -use SP\Core\Exceptions\CheckException; use SP\Core\PhpExtensionChecker; /** @@ -39,16 +38,18 @@ final class ArchiveHandler { public const COMPRESS_EXTENSION = '.tar.gz'; - private PhpExtensionChecker $extensionChecker; - private FileHandler $archive; + private PharData $archive; + /** + * @throws \SP\Core\Exceptions\CheckException + */ public function __construct( - string $archive, + string $archive, PhpExtensionChecker $extensionChecker - ) - { - $this->extensionChecker = $extensionChecker; - $this->archive = new FileHandler(self::makeArchiveName($archive)); + ) { + $extensionChecker->checkPharAvailable(true); + + $this->archive = new PharData(self::makeArchiveName($archive)); } private static function makeArchiveName(string $archive): string @@ -61,51 +62,40 @@ final class ArchiveHandler if (is_file($archive)) { return substr( - $archive, - 0, - strrpos($archive, '.') - ) . $archiveExtension; + $archive, + 0, + strrpos($archive, '.') ?: strlen($archive) + ).$archiveExtension; } - return $archive . $archiveExtension; + return $archive.$archiveExtension; } /** * Realizar un backup de la aplicación y comprimirlo. * - * @throws CheckException * @throws FileException */ - public function compressDirectory( - string $directory, - ?string $regex = null - ): void + public function compressDirectory(string $directory, ?string $regex = null): void { - $this->extensionChecker->checkPharAvailable(true); - - $archive = new PharData($this->archive->getFile()); - $archive->buildFromDirectory($directory, $regex); - $archive->compress(Phar::GZ); + $this->archive->buildFromDirectory($directory, $regex); + $this->archive->compress(Phar::GZ); // Delete the non-compressed archive - $this->archive->delete(); + (new FileHandler($this->archive->getPath()))->delete(); } /** * Realizar un backup de la aplicación y comprimirlo. * - * @throws CheckException * @throws FileException */ public function compressFile(string $file): void { - $this->extensionChecker->checkPharAvailable(true); - - $archive = new PharData($this->archive->getFile()); - $archive->addFile($file, basename($file)); - $archive->compress(Phar::GZ); + $this->archive->addFile($file, basename($file)); + $this->archive->compress(Phar::GZ); // Delete the non-compressed archive - $this->archive->delete(); + (new FileHandler($this->archive->getPath()))->delete(); } } \ No newline at end of file diff --git a/tests/SP/BypassFinalHook.php b/tests/SP/BypassFinalHook.php index 19e380f3..73f5be08 100644 --- a/tests/SP/BypassFinalHook.php +++ b/tests/SP/BypassFinalHook.php @@ -27,10 +27,14 @@ namespace SP\Tests; use DG\BypassFinals; use PHPUnit\Runner\BeforeTestHook; +/** + * + */ final class BypassFinalHook implements BeforeTestHook { public function executeBeforeTest(string $test): void { BypassFinals::enable(); + BypassFinals::setWhitelist([APP_ROOT.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'*']); } } \ No newline at end of file diff --git a/tests/SP/Modules/Cli/Commands/BackupCommandTest.php b/tests/SP/Modules/Cli/Commands/BackupCommandTest.php index b2b9fdc8..5e43422e 100644 --- a/tests/SP/Modules/Cli/Commands/BackupCommandTest.php +++ b/tests/SP/Modules/Cli/Commands/BackupCommandTest.php @@ -29,7 +29,7 @@ use DI\NotFoundException; use SP\Config\Config; use SP\Core\Exceptions\FileNotFoundException; use SP\Modules\Cli\Commands\BackupCommand; -use SP\Services\Backup\FileBackupService; +use SP\Services\Backup\BackupFiles; use SP\Tests\Modules\Cli\CliTestCase; use function SP\Tests\recreateDir; @@ -96,14 +96,14 @@ class BackupCommandTest extends CliTestCase $configData = self::$dic->get(Config::class)->getConfigData(); $this->assertFileExists( - FileBackupService::getAppBackupFilename( + BackupFiles::getAppBackupFilename( TMP_PATH, $configData->getBackupHash(), true ) ); $this->assertFileExists( - FileBackupService::getDbBackupFilename( + BackupFiles::getDbBackupFilename( TMP_PATH, $configData->getBackupHash(), true diff --git a/tests/SP/Services/Backup/FileBackupServiceTest.php b/tests/SP/Services/Backup/FileBackupServiceTest.php index b1b64abd..26c010de 100644 --- a/tests/SP/Services/Backup/FileBackupServiceTest.php +++ b/tests/SP/Services/Backup/FileBackupServiceTest.php @@ -24,34 +24,69 @@ namespace SP\Tests\Services\Backup; -use DI\DependencyException; -use DI\NotFoundException; -use PHPUnit\Framework\TestCase; -use SP\Core\Context\ContextException; +use SP\Core\PhpExtensionChecker; +use SP\Services\Backup\BackupFiles; use SP\Services\Backup\FileBackupService; -use SP\Services\ServiceException; -use function SP\Tests\setupContext; +use SP\Storage\Database\Database; +use SP\Storage\Database\DatabaseUtil; +use SP\Storage\Database\MySQLHandler; +use SP\Storage\File\ArchiveHandler; +use SP\Tests\UnitaryTestCase; /** * Class FileBackupServiceTest * * @package SP\Tests\Services\Backup */ -class FileBackupServiceTest extends TestCase +class FileBackupServiceTest extends UnitaryTestCase { - /** - * @throws DependencyException - * @throws NotFoundException - * @throws ContextException - * @throws ServiceException - */ - public function testDoBackup() - { - $dic = setupContext(); - $service = $dic->get(FileBackupService::class); - $service->doBackup(TMP_PATH); + private FileBackupService $fileBackupService; + private BackupFiles $backupFiles; - $this->assertFileExists(FileBackupService::getAppBackupFilename(TMP_PATH, $service->getHash(), true)); - $this->assertFileExists(FileBackupService::getDbBackupFilename(TMP_PATH, $service->getHash(), true)); + /** + * @throws \SP\Services\ServiceException + */ + public function testDoBackup(): void + { + $this->fileBackupService->doBackup(TMP_PATH, APP_ROOT); + } + + /** + * @throws \SP\Core\Exceptions\ConfigException + * @throws \SP\Core\Context\ContextException + */ + protected function setUp(): void + { + parent::setUp(); + + $database = $this->createStub(Database::class); + $database->method('getDbHandler')->willReturn( + $this->createStub(MySQLHandler::class) + ); + + $archiveHandler = $this->createMock(ArchiveHandler::class); + $archiveHandler->expects(self::once()) + ->method('compressFile') + ->withAnyParameters(); + $archiveHandler->expects(self::once()) + ->method('compressDirectory') + ->with( + APP_ROOT, + FileBackupService::BACKUP_INCLUDE_REGEX + ); + + $this->backupFiles = $this->getMockBuilder(BackupFiles::class) + ->onlyMethods(['getDbBackupArchiveHandler', 'getAppBackupArchiveHandler']) + ->setConstructorArgs([new PhpExtensionChecker()]) + ->getMock(); + $this->backupFiles->method('getDbBackupArchiveHandler')->willReturn($archiveHandler); + $this->backupFiles->method('getAppBackupArchiveHandler')->willReturn($archiveHandler); + + $this->fileBackupService = new FileBackupService( + $this->application, + $database, + $this->createStub(DatabaseUtil::class), + $this->backupFiles + ); } } diff --git a/tests/SP/UnitaryTestCase.php b/tests/SP/UnitaryTestCase.php index 11e1e5e8..9b6f978d 100644 --- a/tests/SP/UnitaryTestCase.php +++ b/tests/SP/UnitaryTestCase.php @@ -29,7 +29,9 @@ use Faker\Factory; use Faker\Generator; use PHPUnit\Framework\TestCase; use SP\Config\Config; +use SP\Core\Application; use SP\Core\Context\StatelessContext; +use SP\Core\Events\EventDispatcher; use SP\Services\Config\ConfigBackupService; use SP\Services\User\UserLoginResponse; use SP\Storage\File\FileCache; @@ -42,6 +44,7 @@ abstract class UnitaryTestCase extends TestCase { protected static Generator $faker; protected Config $config; + protected Application $application; public static function setUpBeforeClass(): void { @@ -56,7 +59,8 @@ abstract class UnitaryTestCase extends TestCase */ protected function setUp(): void { - $this->config = $this->getConfig(); + $this->application = $this->mockApplication(); + $this->config = $this->application->getConfig(); parent::setUp(); } @@ -65,7 +69,7 @@ abstract class UnitaryTestCase extends TestCase * @throws \SP\Core\Exceptions\ConfigException * @throws \SP\Core\Context\ContextException */ - private function getConfig(): Config + private function mockApplication(): Application { $userLogin = new UserLoginResponse(); $userLogin->setLogin(self::$faker->userName); @@ -74,11 +78,13 @@ abstract class UnitaryTestCase extends TestCase $context->initialize(); $context->setUserData($userLogin); - return new Config( + $config = new Config( $this->createStub(XmlHandler::class), $this->createStub(FileCache::class), $context, $this->createStub(ConfigBackupService::class) ); + + return new Application($config, $this->createStub(EventDispatcher::class), $context); } } \ No newline at end of file diff --git a/tests/phpunit.xml b/tests/phpunit.xml index ea2fb099..d2772783 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -5,8 +5,8 @@ bootstrap="./SP/bootstrap.php" colors="true" convertErrorsToExceptions="true" - convertNoticesToExceptions="true" - convertWarningsToExceptions="true" + convertNoticesToExceptions="false" + convertWarningsToExceptions="false" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">