diff --git a/.travis.yml b/.travis.yml index 6730e94a..7c47fc1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 7.0 - 7.1 - 7.2 + - 7.3 matrix: fast_finish: true @@ -16,12 +17,6 @@ matrix: - php: 7.0 env: COVERAGE=1 - - php: 7.0 - env: CAKEPHP_VERSION="3.5.*" - - - php: 7.0 - env: CAKEPHP_VERSION="3.6.*" - services: - mysql - postgresql @@ -32,7 +27,6 @@ addons: install: - composer self-update - composer install --prefer-dist --no-interaction - - if [[ ! -z "$CAKEPHP_VERSION" ]]; then composer require --update-with-dependencies cakephp/cakephp:${CAKEPHP_VERSION}; fi before_script: - mysql -e 'create database test;' diff --git a/CHANGELOG.md b/CHANGELOG.md index bb41acce..6d3ba310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,18 @@ # 2.x branch +## 2.6 branch +### 2.6.0 +* `BackupShell` has been replaced with console commands. Every method of the + previous class is now a `Command` class; +* `BackupManager::index()` returns a collection of backups; +* `ConsoleIntegrationTestCase` has been replaced by `ConsoleIntegrationTestTrait`. + `TestCaseTrait` has been removed and its methods moved to `TestCase`; +* removed `DATABASE_BACKUP` constant; +* updated for CakePHP 3.7. + ## 2.5 branch ### 2.5.1 -* added `Plugin` class for CakePHP 3.6. +* updated for CakePHP 3.6 and 3.7. Added `Plugin` class; +* many small code fixes. ### 2.5.0 * now it uses the `mirko-pagliai/php-tools` package. This also replaces @@ -15,7 +26,7 @@ * `VALID_COMPRESSIONS` and `VALID_EXTENSIONS` constants have been replaced by `getValidCompressions()` and `getValidExtensions()` methods provided by the `BackupTrait` class; -* replaced `InternalErrorException` with `InvalidArgumentException` and +* replaced `InternalErrorException` with `InvalidArgumentException` and `RuntimeException`. This allows compatibility with CakePHP 3.6 branch. ## 2.3 branch diff --git a/README.md b/README.md index 0c1c1456..a7550073 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ Currently, the plugin supports *MySql*, *Postgres* and *Sqlite* databases. Did you like this plugin? Its development requires a lot of time for me. -Please consider the possibility of making [a donation](//paypal.me/mirkopagliai): even a coffee is enough! Thank you. +Please consider the possibility of making [a donation](//paypal.me/mirkopagliai): +even a coffee is enough! Thank you. [![Make a donation](https://www.paypalobjects.com/webstatic/mktg/logo-center/logo_paypal_carte.jpg)](//paypal.me/mirkopagliai) @@ -18,7 +19,7 @@ You can install the plugin via composer: $ composer require --prefer-dist mirko-pagliai/cakephp-database-backup -**NOTE: the latest version available requires at least CakePHP 3.5**. +**NOTE: the latest version available requires at least CakePHP 3.7**. Instead, the [cakephp3.2](//github.com/mirko-pagliai/cakephp-database-backup/tree/cakephp3.2) branch is compatible with all previous versions of CakePHP from version 3.2. diff --git a/appveyor.yml b/appveyor.yml index f958e26d..06611a45 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -27,7 +27,7 @@ before_test: install: - cd c:\ - - curl -fsS -o php.zip https://windows.php.net/downloads/releases/php-5.6.38-nts-Win32-VC11-x86.zip + - curl -fsS -o php.zip https://windows.php.net/downloads/releases/php-5.6.39-nts-Win32-VC11-x86.zip - 7z x php.zip -oc:\php > nul - cd c:\php - copy php.ini-production php.ini diff --git a/composer.json b/composer.json index c6592f81..e9144aa6 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,8 @@ }], "require": { "php": ">=5.5.9", - "cakephp/cakephp": "^3.5", - "mirko-pagliai/php-tools": "^1.0" + "cakephp/cakephp": "^3.7", + "mirko-pagliai/php-tools": "^1.1" }, "require-dev": { "cakephp/cakephp-codesniffer": "^3.0", @@ -24,6 +24,7 @@ }, "autoload-dev": { "psr-4": { + "App\\": "tests/test_app/TestApp/", "Cake\\Test\\": "vendor/cakephp/cakephp/tests", "DatabaseBackup\\Test\\": "tests" } diff --git a/config/bootstrap.php b/config/bootstrap.php index ec89f765..d88d2866 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -13,11 +13,6 @@ */ use Cake\Core\Configure; -//Sets the default DatabaseBackup name -if (!defined('DATABASE_BACKUP')) { - define('DATABASE_BACKUP', 'DatabaseBackup'); -} - //Sets the redirect to `/dev/null`. This string can be concatenated to shell commands if (!defined('REDIRECT_TO_DEV_NULL')) { define('REDIRECT_TO_DEV_NULL', is_win() ? ' 2>nul' : ' 2>/dev/null'); @@ -25,38 +20,35 @@ //Binaries foreach (['bzip2', 'gzip', 'mysql', 'mysqldump', 'pg_dump', 'pg_restore', 'sqlite3'] as $binary) { - if (!Configure::check(DATABASE_BACKUP . '.binaries.' . $binary)) { - Configure::write(DATABASE_BACKUP . '.binaries.' . $binary, which($binary)); + if (!Configure::check('DatabaseBackup.binaries.' . $binary)) { + Configure::write('DatabaseBackup.binaries.' . $binary, which($binary)); } } //Chmod for backups. //This works only on Unix -if (!Configure::check(DATABASE_BACKUP . '.chmod')) { - Configure::write(DATABASE_BACKUP . '.chmod', 0664); +if (!Configure::check('DatabaseBackup.chmod')) { + Configure::write('DatabaseBackup.chmod', 0664); } //Database connection -if (!Configure::check(DATABASE_BACKUP . '.connection')) { - Configure::write(DATABASE_BACKUP . '.connection', 'default'); +if (!Configure::check('DatabaseBackup.connection')) { + Configure::write('DatabaseBackup.connection', 'default'); } //Redirects stderr to `/dev/null`. This suppresses the output of executed commands -if (!Configure::check(DATABASE_BACKUP . '.redirectStderrToDevNull')) { - Configure::write(DATABASE_BACKUP . '.redirectStderrToDevNull', true); +if (!Configure::check('DatabaseBackup.redirectStderrToDevNull')) { + Configure::write('DatabaseBackup.redirectStderrToDevNull', true); } //Default target directory -if (!Configure::check(DATABASE_BACKUP . '.target')) { - Configure::write(DATABASE_BACKUP . '.target', ROOT . DS . 'backups'); +if (!Configure::check('DatabaseBackup.target')) { + Configure::write('DatabaseBackup.target', ROOT . DS . 'backups'); } //Checks for the target directory -$target = Configure::read(DATABASE_BACKUP . '.target'); - -if (!file_exists($target)) { - safe_mkdir($target); -} +$target = Configure::read('DatabaseBackup.target'); +safe_mkdir($target); if (!is_writeable($target)) { trigger_error(sprintf('Directory %s not writeable', $target), E_USER_ERROR); diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 95831458..407265c7 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -2,9 +2,13 @@ . - + + + 0 + + 0 diff --git a/src/BackupTrait.php b/src/BackupTrait.php index 1935f2c1..e44f9243 100644 --- a/src/BackupTrait.php +++ b/src/BackupTrait.php @@ -50,7 +50,7 @@ public function getAbsolutePath($path) */ public function getBinary($name) { - return Configure::readOrFail(DATABASE_BACKUP . '.binaries.' . $name); + return Configure::readOrFail('DatabaseBackup.binaries.' . $name); } /** @@ -79,7 +79,7 @@ public function getCompression($filename) */ public function getConnection($name = null) { - return ConnectionManager::get($name ?: Configure::readOrFail(DATABASE_BACKUP . '.connection')); + return ConnectionManager::get($name ?: Configure::readOrFail('DatabaseBackup.connection')); } /** @@ -95,7 +95,7 @@ public function getDriver(ConnectionInterface $connection = null) { $connection = $connection ?: $this->getConnection(); $className = get_class_short_name($connection->getDriver()); - $driver = App::classname(sprintf('%s.%s', DATABASE_BACKUP, $className), 'Driver'); + $driver = App::classname(sprintf('%s.%s', 'DatabaseBackup', $className), 'Driver'); if (!$driver) { throw new InvalidArgumentException(__d('database_backup', 'The `{0}` driver does not exist', $className)); @@ -124,7 +124,7 @@ public function getExtension($filename) */ public function getTarget() { - return Configure::read(DATABASE_BACKUP . '.target'); + return Configure::read('DatabaseBackup.target'); } /** diff --git a/src/Command/DeleteAllCommand.php b/src/Command/DeleteAllCommand.php new file mode 100644 index 00000000..7d541df7 --- /dev/null +++ b/src/Command/DeleteAllCommand.php @@ -0,0 +1,65 @@ +setDescription(__d('database_backup', 'Deletes all database backups')); + + return $parser; + } + + /** + * Deletes all backup files + * @param Arguments $args The command arguments + * @param ConsoleIo $io The console io + * @return null|int The exit code or null for success + * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#delete_all + * @uses DatabaseBackup\Utility\BackupManager::deleteAll() + */ + public function execute(Arguments $args, ConsoleIo $io) + { + parent::execute($args, $io); + + $deleted = (new BackupManager)->deleteAll(); + + if (!$deleted) { + $io->verbose(__d('database_backup', 'No backup has been deleted')); + + return null; + } + + foreach ($deleted as $file) { + $io->verbose(__d('database_backup', 'Backup `{0}` has been deleted', rtr($file))); + } + + $io->success(__d('database_backup', 'Deleted backup files: {0}', count($deleted))); + } +} diff --git a/src/Command/ExportCommand.php b/src/Command/ExportCommand.php new file mode 100644 index 00000000..1e8202c6 --- /dev/null +++ b/src/Command/ExportCommand.php @@ -0,0 +1,125 @@ +setDescription(__d('database_backup', 'Exports a database backup')); + $parser->addOptions([ + 'compression' => [ + 'choices' => $this->getValidCompressions(), + 'help' => __d('database_backup', 'Compression type. By default, no compression will be used'), + 'short' => 'c', + ], + 'filename' => [ + 'help' => __d('database_backup', 'Filename. It can be an absolute path and may contain ' . + 'patterns. The compression type will be automatically setted'), + 'short' => 'f', + ], + 'rotate' => [ + 'help' => __d('database_backup', 'Rotates backups. You have to indicate the number of backups you ' . + 'want to keep. So, it will delete all backups that are older. By default, no backup will be deleted'), + 'short' => 'r', + ], + 'send' => [ + 'help' => __d('database_backup', 'Sends the backup file via email. You have ' . + 'to indicate the recipient\'s email address'), + 'short' => 's', + ], + ]); + + return $parser; + } + + /** + * Exports a database backup. + * + * This command uses `RotateCommand` and `SendCommand`. + * @param Arguments $args The command arguments + * @param ConsoleIo $io The console io + * @return null|int The exit code or null for success + * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#export + * @uses DatabaseBackup\Command\RotateCommand::execute() + * @uses DatabaseBackup\Command\SendCommand::execute() + * @uses DatabaseBackup\Utility\BackupExport::compression() + * @uses DatabaseBackup\Utility\BackupExport::export() + * @uses DatabaseBackup\Utility\BackupExport::filename() + */ + public function execute(Arguments $args, ConsoleIo $io) + { + parent::execute($args, $io); + + try { + $instance = new BackupExport; + //Sets the output filename or the compression type. + //Regarding the `rotate` option, the `BackupShell::rotate()` method + // will be called at the end, instead of `BackupExport::rotate()` + if ($args->hasOption('filename')) { + $instance->filename($args->getOption('filename')); + } elseif ($args->hasOption('compression')) { + $instance->compression($args->getOption('compression')); + } + + //Exports + $file = $instance->export(); + $io->success(__d('database_backup', 'Backup `{0}` has been exported', rtr($file))); + + //Sends via email + if ($args->hasOption('send')) { + $SendCommand = new SendCommand; + $sendArgs = new Arguments( + [$file, $args->getOption('send')], + ['verbose' => $args->getOption('verbose'), 'quiet' => $args->getOption('quiet')], + $SendCommand->getOptionParser()->argumentNames() + ); + $SendCommand->execute($sendArgs, $io); + } + + //Rotates + if ($args->hasOption('rotate')) { + $RotateCommand = new RotateCommand; + $rotateArgs = new Arguments( + [$args->getOption('rotate')], + ['verbose' => $args->getOption('verbose'), 'quiet' => $args->getOption('quiet')], + $RotateCommand->getOptionParser()->argumentNames() + ); + $RotateCommand->execute($rotateArgs, $io); + } + } catch (Exception $e) { + $io->error($e->getMessage()); + $this->abort(); + } + + return null; + } +} diff --git a/src/Command/ImportCommand.php b/src/Command/ImportCommand.php new file mode 100644 index 00000000..db484dbc --- /dev/null +++ b/src/Command/ImportCommand.php @@ -0,0 +1,68 @@ +setDescription(__d('database_backup', 'Imports a database backup')); + $parser->addArgument('filename', [ + 'help' => __d('database_backup', 'Filename. It can be an absolute path'), + 'required' => true, + ]); + + return $parser; + } + + /** + * Imports a database backup + * @param Arguments $args The command arguments + * @param ConsoleIo $io The console io + * @return null|int The exit code or null for success + * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#import + * @uses DatabaseBackup\Utility\BackupImport::filename() + * @uses DatabaseBackup\Utility\BackupImport::import() + */ + public function execute(Arguments $args, ConsoleIo $io) + { + parent::execute($args, $io); + + try { + $file = (new BackupImport)->filename($args->getArgument('filename'))->import(); + + $io->success(__d('database_backup', 'Backup `{0}` has been imported', rtr($file))); + } catch (Exception $e) { + $io->error($e->getMessage()); + $this->abort(); + } + + return null; + } +} diff --git a/src/Command/IndexCommand.php b/src/Command/IndexCommand.php new file mode 100644 index 00000000..943c540f --- /dev/null +++ b/src/Command/IndexCommand.php @@ -0,0 +1,79 @@ +setDescription(__d('database_backup', 'Lists database backups')); + + return $parser; + } + + /** + * Lists database backups + * @param Arguments $args The command arguments + * @param ConsoleIo $io The console io + * @return null|int The exit code or null for success + * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#index + * @uses DatabaseBackup\Utility\BackupManager::index() + */ + public function execute(Arguments $args, ConsoleIo $io) + { + parent::execute($args, $io); + + //Gets all backups + $backups = (new BackupManager)->index(); + + $io->out(__d('database_backup', 'Backup files found: {0}', $backups->count())); + + if ($backups->isEmpty()) { + return null; + } + + //Parses backups + $backups = $backups->map(function ($backup) { + $backup->size = Number::toReadableSize($backup->size); + + return array_values($backup->toArray()); + })->toList(); + + //Table headers + $headers = [ + __d('database_backup', 'Filename'), + __d('database_backup', 'Extension'), + __d('database_backup', 'Compression'), + __d('database_backup', 'Size'), + __d('database_backup', 'Datetime'), + ]; + + $io->helper('table')->output(array_merge([$headers], $backups)); + } +} diff --git a/src/Command/RotateCommand.php b/src/Command/RotateCommand.php new file mode 100644 index 00000000..7fe11d6f --- /dev/null +++ b/src/Command/RotateCommand.php @@ -0,0 +1,79 @@ +setDescription(__d('database_backup', 'Rotates backups')); + $parser->addArgument('keep', [ + 'help' => __d('database_backup', 'Number of backups you want to keep. So, it will delete all backups that are older'), + 'required' => true, + ]); + + return $parser; + } + + /** + * Rotates backups. + * + * You have to indicate the number of backups you want to keep. So, it will + * delete all backups that are older. By default, no backup will be deleted + * @param Arguments $args The command arguments + * @param ConsoleIo $io The console io + * @return null|int The exit code or null for success + * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#rotate + * @uses DatabaseBackup\Utility\BackupManager::rotate() + */ + public function execute(Arguments $args, ConsoleIo $io) + { + parent::execute($args, $io); + + try { + //Gets deleted files + $deleted = (new BackupManager)->rotate($args->getArgument('keep')); + + if (empty($deleted)) { + $io->verbose(__d('database_backup', 'No backup has been deleted')); + + return null; + } + + foreach ($deleted as $file) { + $io->verbose(__d('database_backup', 'Backup `{0}` has been deleted', $file->filename)); + } + + $io->success(__d('database_backup', 'Deleted backup files: {0}', count($deleted))); + } catch (Exception $e) { + $io->error($e->getMessage()); + $this->abort(); + } + } +} diff --git a/src/Command/SendCommand.php b/src/Command/SendCommand.php new file mode 100644 index 00000000..45a024a6 --- /dev/null +++ b/src/Command/SendCommand.php @@ -0,0 +1,73 @@ +setDescription(__d('database_backup', 'Send a database backup via mail')); + $parser->addArguments([ + 'filename' => [ + 'help' => __d('database_backup', 'Filename. It can be an absolute path'), + 'required' => true, + ], + 'recipient' => [ + 'help' => __d('database_backup', 'Recipient\'s email address'), + 'required' => true, + ], + ]); + + return $parser; + } + + /** + * Sends a backup file via email + * @param Arguments $args The command arguments + * @param ConsoleIo $io The console io + * @return null|int The exit code or null for success + * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#send + * @uses DatabaseBackup\Utility\BackupManager::send() + */ + public function execute(Arguments $args, ConsoleIo $io) + { + parent::execute($args, $io); + + try { + (new BackupManager)->send($args->getArgument('filename'), $args->getArgument('recipient')); + + $io->success(__d('database_backup', 'Backup `{0}` was sent via mail', rtr($args->getArgument('filename')))); + } catch (Exception $e) { + $io->error($e->getMessage()); + $this->abort(); + } + + return null; + } +} diff --git a/src/Console/Command.php b/src/Console/Command.php new file mode 100644 index 00000000..9f70ccd1 --- /dev/null +++ b/src/Console/Command.php @@ -0,0 +1,45 @@ +getConnection()->config(); + $driver = $this->getDriver($this->getConnection()); + + $io->out(__d('database_backup', 'Connection: {0}', $config['name'])); + $io->out(__d('database_backup', 'Driver: {0}', get_class_short_name($driver))); + $io->hr(); + + return null; + } +} diff --git a/src/Driver/Driver.php b/src/Driver/Driver.php index 31991d58..a775e9b9 100644 --- a/src/Driver/Driver.php +++ b/src/Driver/Driver.php @@ -131,7 +131,7 @@ protected function _exportExecutableWithCompression($filename) $executable .= ' > ' . escapeshellarg($filename); - if (Configure::read(DATABASE_BACKUP . '.redirectStderrToDevNull')) { + if (Configure::read('DatabaseBackup.redirectStderrToDevNull')) { $executable .= REDIRECT_TO_DEV_NULL; } @@ -156,7 +156,7 @@ protected function _importExecutableWithCompression($filename) $executable .= ' < ' . $filename; } - if (Configure::read(DATABASE_BACKUP . '.redirectStderrToDevNull')) { + if (Configure::read('DatabaseBackup.redirectStderrToDevNull')) { $executable .= REDIRECT_TO_DEV_NULL; } diff --git a/src/Plugin.php b/src/Plugin.php index ce8658df..1cb7f5a9 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -14,10 +14,32 @@ namespace DatabaseBackup; use Cake\Core\BasePlugin; +use DatabaseBackup\Command\DeleteAllCommand; +use DatabaseBackup\Command\ExportCommand; +use DatabaseBackup\Command\ImportCommand; +use DatabaseBackup\Command\IndexCommand; +use DatabaseBackup\Command\RotateCommand; +use DatabaseBackup\Command\SendCommand; /** * Plugin class */ class Plugin extends BasePlugin { + /** + * Add console commands for the plugin + * @param Cake\Console\CommandCollection $commands The command collection to update + * @return Cake\Console\CommandCollection + */ + public function console($commands) + { + $commands->add('database_backup.delete_all', DeleteAllCommand::class); + $commands->add('database_backup.export', ExportCommand::class); + $commands->add('database_backup.import', ImportCommand::class); + $commands->add('database_backup.index', IndexCommand::class); + $commands->add('database_backup.rotate', RotateCommand::class); + $commands->add('database_backup.send', SendCommand::class); + + return $commands; + } } diff --git a/src/Shell/BackupShell.php b/src/Shell/BackupShell.php deleted file mode 100644 index 202e5f5f..00000000 --- a/src/Shell/BackupShell.php +++ /dev/null @@ -1,356 +0,0 @@ -BackupManager = new BackupManager; - $this->config = $this->getConnection()->config(); - $this->driver = $this->getDriver($this->getConnection()); - } - - /** - * Displays a header for the shell - * @return void - * @since 2.0.0 - * @uses $config - * @uses $driver - */ - protected function _welcome() - { - parent::_welcome(); - - $this->out(__d('database_backup', 'Connection: {0}', $this->config['name'])); - $this->out(__d('database_backup', 'Driver: {0}', get_class_short_name($this->driver))); - $this->hr(); - } - - /** - * Deletes all backup files - * @return void - * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#deleteAll - * @since 1.0.1 - * @uses DatabaseBackup\Utility\BackupManager::deleteAll() - * @uses $BackupManager - */ - public function deleteAll() - { - $deleted = $this->BackupManager->deleteAll(); - - if (!$deleted) { - $this->verbose(__d('database_backup', 'No backup has been deleted')); - - return; - } - - foreach ($deleted as $file) { - $this->verbose(__d('database_backup', 'Backup `{0}` has been deleted', rtr($file))); - } - - $this->success(__d('database_backup', 'Deleted backup files: {0}', count($deleted))); - } - - /** - * Exports a database backup - * @return void - * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#export - * @uses DatabaseBackup\Utility\BackupExport::compression() - * @uses DatabaseBackup\Utility\BackupExport::export() - * @uses DatabaseBackup\Utility\BackupExport::filename() - * @uses rotate() - * @uses send() - */ - public function export() - { - try { - $instance = new BackupExport; - - //Sets the output filename or the compression type. - //Regarding the `rotate` option, the `BackupShell::rotate()` method - // will be called at the end, instead of `BackupExport::rotate()` - if ($this->param('filename')) { - $instance->filename($this->param('filename')); - } elseif ($this->param('compression')) { - $instance->compression($this->param('compression')); - } - - //Exports - $file = $instance->export(); - - $this->success(__d('database_backup', 'Backup `{0}` has been exported', rtr($file))); - - //Sends via email - if ($this->param('send')) { - $this->send($file, $this->param('send')); - } - - //Rotates - if ($this->param('rotate')) { - $this->rotate($this->param('rotate')); - } - } catch (Exception $e) { - $this->abort($e->getMessage()); - } - } - - /** - * Imports a database backup - * @param string $filename Filename. It can be an absolute path - * @return void - * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#import - * @uses DatabaseBackup\Utility\BackupImport::filename() - * @uses DatabaseBackup\Utility\BackupImport::import() - */ - public function import($filename) - { - try { - $file = (new BackupImport)->filename($filename)->import(); - - $this->success(__d('database_backup', 'Backup `{0}` has been imported', rtr($file))); - } catch (Exception $e) { - $this->abort($e->getMessage()); - } - } - - /** - * Lists database backups - * @return void - * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#index - * @uses DatabaseBackup\Utility\BackupManager::index() - * @uses $BackupManager - */ - public function index() - { - //Gets all backups - $backups = $this->BackupManager->index(); - - $this->out(__d('database_backup', 'Backup files found: {0}', count($backups))); - - if ($backups) { - //Parses backups - $backups = array_map(function ($backup) { - $backup->size = Number::toReadableSize($backup->size); - - return array_values($backup->toArray()); - }, $backups); - - //Table headers - $headers = [ - __d('database_backup', 'Filename'), - __d('database_backup', 'Extension'), - __d('database_backup', 'Compression'), - __d('database_backup', 'Size'), - __d('database_backup', 'Datetime'), - ]; - - $this->helper('table')->output(array_merge([$headers], $backups)); - } - } - - /** - * Main command. Alias for `index()` - * @return void - * @uses index() - */ - public function main() - { - $this->index(); - } - - /** - * Rotates backups. - * - * You have to indicate the number of backups you want to keep. So, it will - * delete all backups that are older. By default, no backup will be deleted - * @param int $keep Number of backups you want to keep - * @return void - * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupShell#rotate - * @uses DatabaseBackup\Utility\BackupManager::rotate() - * @uses $BackupManager - */ - public function rotate($keep) - { - try { - //Gets deleted files - $deleted = $this->BackupManager->rotate($keep); - - if (empty($deleted)) { - $this->verbose(__d('database_backup', 'No backup has been deleted')); - - return; - } - - foreach ($deleted as $file) { - $this->verbose(__d('database_backup', 'Backup `{0}` has been deleted', $file->filename)); - } - - $this->success(__d('database_backup', 'Deleted backup files: {0}', count($deleted))); - } catch (Exception $e) { - $this->abort($e->getMessage()); - } - } - - /** - * Sends a backup file via email - * @param string $filename Filename of the backup that you want to send via - * email. The path can be relative to the backup directory - * @param string $recipient Recipient's email address - * @return void - * @since 1.1.0 - * @uses DatabaseBackup\Utility\BackupManager::send() - * @uses $BackupManager - */ - public function send($filename, $recipient) - { - try { - $this->BackupManager->send($filename, $recipient); - - $this->success(__d('database_backup', 'Backup `{0}` was sent via mail', rtr($filename))); - } catch (Exception $e) { - $this->abort($e->getMessage()); - } - } - - /** - * Gets the option parser instance and configures it - * @return ConsoleOptionParser - * @uses getValidCompressions() - */ - public function getOptionParser() - { - $parser = parent::getOptionParser(); - - $parser->setDescription(__d('database_backup', 'Shell to handle database backups')); - - $parser->addSubcommand('deleteAll', ['help' => __d('database_backup', 'Deletes all database backups')]); - - $parser->addSubcommand('export', [ - 'help' => __d('database_backup', 'Exports a database backup'), - 'parser' => [ - 'options' => [ - 'compression' => [ - 'choices' => $this->getValidCompressions(), - 'help' => __d('database_backup', 'Compression type. By default, no compression will be used'), - 'short' => 'c', - ], - 'filename' => [ - 'help' => __d('database_backup', 'Filename. It can be an absolute path and may contain ' . - 'patterns. The compression type will be automatically setted'), - 'short' => 'f', - ], - 'rotate' => [ - 'help' => __d('database_backup', 'Rotates backups. You have to indicate the number of backups you ' . - 'want to keep. So, it will delete all backups that are older. By default, no backup will be deleted'), - 'short' => 'r', - ], - 'send' => [ - 'help' => __d('database_backup', 'Sends the backup file via email. You have ' . - 'to indicate the recipient\'s email address'), - 'short' => 's', - ], - ], - ], - ]); - - $parser->addSubcommand('index', ['help' => __d('database_backup', 'Lists database backups')]); - - $parser->addSubcommand('import', [ - 'help' => __d('database_backup', 'Imports a database backup'), - 'parser' => [ - 'arguments' => [ - 'filename' => [ - 'help' => __d('database_backup', 'Filename. It can be an absolute path'), - 'required' => true, - ], - ], - ], - ]); - - $parser->addSubcommand('rotate', [ - 'help' => __d('database_backup', 'Rotates backups'), - 'parser' => [ - 'arguments' => [ - 'keep' => [ - 'help' => __d('database_backup', 'Number of backups you want to keep. So, it ' . - 'will delete all backups that are older'), - 'required' => true, - ], - ], - ], - ]); - - $parser->addSubcommand('send', [ - 'help' => __d('database_backup', 'Send a database backup via mail'), - 'parser' => [ - 'arguments' => [ - 'filename' => [ - 'help' => __d('database_backup', 'Filename. It can be an absolute path'), - 'required' => true, - ], - 'recipient' => [ - 'help' => __d('database_backup', 'Recipient\'s email address'), - 'required' => true, - ], - ], - ], - ]); - - return $parser; - } -} diff --git a/src/TestSuite/ConsoleIntegrationTestCase.php b/src/TestSuite/ConsoleIntegrationTestTrait.php similarity index 59% rename from src/TestSuite/ConsoleIntegrationTestCase.php rename to src/TestSuite/ConsoleIntegrationTestTrait.php index 935541ae..3c9e2ce4 100644 --- a/src/TestSuite/ConsoleIntegrationTestCase.php +++ b/src/TestSuite/ConsoleIntegrationTestTrait.php @@ -9,23 +9,22 @@ * @copyright Copyright (c) Mirko Pagliai * @link https://github.com/mirko-pagliai/cakephp-database-backup * @license https://opensource.org/licenses/mit-license.php MIT License - * @since 2.2.0 + * @since 2.6.0 */ namespace DatabaseBackup\TestSuite; use Cake\Console\Shell; -use Cake\Core\Configure; -use Cake\Http\BaseApplication; -use Cake\TestSuite\ConsoleIntegrationTestCase as CakeConsoleIntegrationTestCase; -use DatabaseBackup\TestSuite\TestCaseTrait; +use Cake\TestSuite\ConsoleIntegrationTestTrait as CakeConsoleIntegrationTestTrait; use DatabaseBackup\Utility\BackupExport; /** - * ConsoleIntegrationTestCase class + * ConsoleIntegrationTestTrait class */ -class ConsoleIntegrationTestCase extends CakeConsoleIntegrationTestCase +trait ConsoleIntegrationTestTrait { - use TestCaseTrait; + use CakeConsoleIntegrationTestTrait { + CakeConsoleIntegrationTestTrait::tearDown as cakeTearDown; + } /** * @var \DatabaseBackup\Utility\BackupExport @@ -33,35 +32,17 @@ class ConsoleIntegrationTestCase extends CakeConsoleIntegrationTestCase protected $BackupExport; /** - * Setup the test case, backup the static object values so they can be - * restored. Specifically backs up the contents of Configure and paths in - * App if they have not already been backed up + * Called before every test method * @return void */ public function setUp() { parent::setUp(); - $app = $this->getMockForAbstractClass(BaseApplication::class, ['']); - $app->addPlugin('DatabaseBackup')->pluginBootstrap(); - + $this->useCommandRunner(); $this->BackupExport = new BackupExport; } - /** - * Teardown any static object changes and restore them - * @return void - * @uses deleteAllBackups() - */ - public function tearDown() - { - parent::tearDown(); - - $this->deleteAllBackups(); - - Configure::write(DATABASE_BACKUP . '.connection', 'test'); - } - /** * Asserts shell exited with the error code * @param string $message Failure message to be appended to the generated diff --git a/src/TestSuite/DriverTestCase.php b/src/TestSuite/DriverTestCase.php index 7b007ec0..5385e359 100644 --- a/src/TestSuite/DriverTestCase.php +++ b/src/TestSuite/DriverTestCase.php @@ -16,7 +16,6 @@ use Cake\Core\Configure; use Cake\Database\Connection; use Cake\Event\EventList; -use Cake\Http\BaseApplication; use Cake\ORM\TableRegistry; use DatabaseBackup\BackupTrait; use DatabaseBackup\TestSuite\TestCase; @@ -60,7 +59,6 @@ abstract class DriverTestCase extends TestCase /** * Name of the database connection - * @since 2.5.2 * @var string */ protected $connection; @@ -72,19 +70,14 @@ abstract class DriverTestCase extends TestCase public $fixtures; /** - * Setup the test case, backup the static object values so they can be - * restored. Specifically backs up the contents of Configure and paths in - * App if they have not already been backed up + * Called before every test method * @return void */ public function setUp() { parent::setUp(); - $app = $this->getMockForAbstractClass(BaseApplication::class, ['']); - $app->addPlugin('DatabaseBackup')->pluginBootstrap(); - - Configure::write(DATABASE_BACKUP . '.connection', $this->connection); + Configure::write('DatabaseBackup.connection', $this->connection); $connection = $this->getConnection(); diff --git a/src/TestSuite/TestCase.php b/src/TestSuite/TestCase.php index 2a57b198..7e9abdc6 100644 --- a/src/TestSuite/TestCase.php +++ b/src/TestSuite/TestCase.php @@ -14,10 +14,8 @@ namespace DatabaseBackup\TestSuite; use Cake\Core\Configure; -use Cake\Http\BaseApplication; use Cake\TestSuite\TestCase as CakeTestCase; -use DatabaseBackup\TestSuite\TestCaseTrait; -use Tools\TestSuite\TestCaseTrait as ToolsTestCaseTrait; +use Tools\TestSuite\TestCaseTrait; /** * TestCase class @@ -25,26 +23,21 @@ abstract class TestCase extends CakeTestCase { use TestCaseTrait; - use ToolsTestCaseTrait; /** - * Setup the test case, backup the static object values so they can be - * restored. Specifically backs up the contents of Configure and paths in - * App if they have not already been backed up + * Called before every test method * @return void */ public function setUp() { parent::setUp(); - $app = $this->getMockForAbstractClass(BaseApplication::class, ['']); - $app->addPlugin('DatabaseBackup')->pluginBootstrap(); - - $this->deleteAllBackups(); + Configure::write('DatabaseBackup.connection', 'test'); + $this->loadPlugins(['DatabaseBackup']); } /** - * Teardown any static object changes and restore them + * Called after every test method * @return void * @uses deleteAllBackups() */ @@ -52,6 +45,48 @@ public function tearDown() { parent::tearDown(); - Configure::write(DATABASE_BACKUP . '.connection', 'test'); + $this->deleteAllBackups(); + } + + /** + * Internal method to create a backup file + * @return string + */ + protected function createBackup() + { + return $this->BackupExport->filename('backup.sql')->export(); + } + + /** + * Internal method to creates some backup files + * @param bool $sleep If `true`, waits a second for each backup + * @return array + */ + protected function createSomeBackups($sleep = false) + { + $files[] = $this->BackupExport->filename('backup.sql')->export(); + + if ($sleep) { + sleep(1); + } + + $files[] = $this->BackupExport->filename('backup.sql.bz2')->export(); + + if ($sleep) { + sleep(1); + } + + $files[] = $this->BackupExport->filename('backup.sql.gz')->export(); + + return $files; + } + + /** + * Internal method to deletes all backups + * @return void + */ + public function deleteAllBackups() + { + safe_unlink_recursive(Configure::read('DatabaseBackup.target')); } } diff --git a/src/TestSuite/TestCaseTrait.php b/src/TestSuite/TestCaseTrait.php deleted file mode 100644 index 326cbe1c..00000000 --- a/src/TestSuite/TestCaseTrait.php +++ /dev/null @@ -1,64 +0,0 @@ -BackupExport->filename('backup.sql')->export(); - } - - /** - * Internal method to creates some backup files - * @param bool $sleep If `true`, waits a second for each backup - * @return array - */ - protected function createSomeBackups($sleep = false) - { - $files[] = $this->BackupExport->filename('backup.sql')->export(); - - if ($sleep) { - sleep(1); - } - - $files[] = $this->BackupExport->filename('backup.sql.bz2')->export(); - - if ($sleep) { - sleep(1); - } - - $files[] = $this->BackupExport->filename('backup.sql.gz')->export(); - - return $files; - } - - /** - * Deletes all backups - * @return void - */ - public function deleteAllBackups() - { - safe_unlink_recursive(Configure::read(DATABASE_BACKUP . '.target')); - } -} diff --git a/src/Utility/BackupExport.php b/src/Utility/BackupExport.php index 3091f147..c57f874a 100644 --- a/src/Utility/BackupExport.php +++ b/src/Utility/BackupExport.php @@ -226,7 +226,7 @@ public function export() $this->driver->export($filename); if (!is_win()) { - chmod($filename, Configure::read(DATABASE_BACKUP . '.chmod')); + chmod($filename, Configure::read('DatabaseBackup.chmod')); } if ($this->emailRecipient) { diff --git a/src/Utility/BackupManager.php b/src/Utility/BackupManager.php index 019d331f..d06014b6 100644 --- a/src/Utility/BackupManager.php +++ b/src/Utility/BackupManager.php @@ -20,7 +20,6 @@ use Cake\ORM\Entity; use DatabaseBackup\BackupTrait; use InvalidArgumentException; -use RuntimeException; /** * Utility to manage database backups @@ -56,7 +55,7 @@ public function deleteAll() { $deleted = []; - foreach ($this->index() as $file) { + foreach ($this->index()->toList() as $file) { if ($this->delete($file->filename)) { $deleted[] = $file->filename; } @@ -67,7 +66,8 @@ public function deleteAll() /** * Returns a list of database backups - * @return array Backups as entities + * @return Cake\Collection\Collection Collection of backups . Each backup + * is an entity * @see https://github.com/mirko-pagliai/cakephp-database-backup/wiki/How-to-use-the-BackupManager-utility#index */ public function index() @@ -84,8 +84,7 @@ public function index() 'datetime' => new FrozenTime(date('Y-m-d H:i:s', filemtime($target . DS . $filename))), ]); }) - ->sortBy('datetime') - ->toList(); + ->sortBy('datetime'); } /** @@ -106,14 +105,14 @@ public function rotate($rotate) throw new InvalidArgumentException(__d('database_backup', 'Invalid rotate value')); } - $backupsToBeDeleted = array_slice($this->index(), $rotate); + $backupsToBeDeleted = $this->index()->skip($rotate); //Deletes foreach ($backupsToBeDeleted as $backup) { $this->delete($backup->filename); } - return $backupsToBeDeleted; + return $backupsToBeDeleted->toArray(); } /** @@ -133,7 +132,7 @@ protected function getEmailInstance($backup, $recipient) $mimetype = mime_content_type($file); return (new Email) - ->setFrom(Configure::readOrFail(DATABASE_BACKUP . '.mailSender')) + ->setFrom(Configure::readOrFail('DatabaseBackup.mailSender')) ->setTo($recipient) ->setSubject(__d('database_backup', 'Database backup {0} from {1}', $basename, env('SERVER_NAME', 'localhost'))) ->setAttachments([$basename => compact('file', 'mimetype')]); diff --git a/tests/TestCase/BackupTraitTest.php b/tests/TestCase/BackupTraitTest.php index 4659a401..9264160d 100644 --- a/tests/TestCase/BackupTraitTest.php +++ b/tests/TestCase/BackupTraitTest.php @@ -48,10 +48,10 @@ public function testGetAbsolutePath() $this->assertEquals('/file.txt', $result); $result = $this->getAbsolutePath('file.txt'); - $this->assertEquals(Configure::read(DATABASE_BACKUP . '.target') . DS . 'file.txt', $result); + $this->assertEquals(Configure::read('DatabaseBackup.target') . DS . 'file.txt', $result); - $result = $this->getAbsolutePath(Configure::read(DATABASE_BACKUP . '.target') . DS . 'file.txt'); - $this->assertEquals(Configure::read(DATABASE_BACKUP . '.target') . DS . 'file.txt', $result); + $result = $this->getAbsolutePath(Configure::read('DatabaseBackup.target') . DS . 'file.txt'); + $this->assertEquals(Configure::read('DatabaseBackup.target') . DS . 'file.txt', $result); } /** @@ -102,7 +102,7 @@ public function testGetConnection() foreach ([ null, - Configure::read(DATABASE_BACKUP . '.connection'), + Configure::read('DatabaseBackup.connection'), 'fake', ] as $name) { $connection = $this->getConnection($name); @@ -129,10 +129,10 @@ public function testGetConnectionInvalidConnection() public function testGetDriver() { $driver = $this->getDriver(ConnectionManager::get('test')); - $this->assertInstanceof(DATABASE_BACKUP . '\Driver\Mysql', $driver); + $this->assertInstanceof('DatabaseBackup\Driver\Mysql', $driver); $driver = $this->getDriver(); - $this->assertInstanceof(DATABASE_BACKUP . '\Driver\Mysql', $driver); + $this->assertInstanceof('DatabaseBackup\Driver\Mysql', $driver); } /** @@ -183,7 +183,7 @@ public function testGetDriverNoExistingDriver() */ public function testGetTarget() { - $this->assertEquals(Configure::read(DATABASE_BACKUP . '.target'), $this->getTarget()); + $this->assertEquals(Configure::read('DatabaseBackup.target'), $this->getTarget()); } /** diff --git a/tests/TestCase/Command/DeleteAllCommandTest.php b/tests/TestCase/Command/DeleteAllCommandTest.php new file mode 100644 index 00000000..cbad2d1f --- /dev/null +++ b/tests/TestCase/Command/DeleteAllCommandTest.php @@ -0,0 +1,50 @@ +exec(self::$command); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Connection: test'); + $this->assertOutputContains('Driver: Mysql'); + $this->assertOutputContains('No backup has been deleted'); + + $this->createSomeBackups(true); + $this->exec(self::$command); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Backup `backup.sql.gz` has been deleted'); + $this->assertOutputContains('Backup `backup.sql.bz2` has been deleted'); + $this->assertOutputContains('Backup `backup.sql` has been deleted'); + $this->assertOutputContains('Deleted backup files: 3'); + } +} diff --git a/tests/TestCase/Command/ExportCommandTest.php b/tests/TestCase/Command/ExportCommandTest.php new file mode 100644 index 00000000..9f2eec91 --- /dev/null +++ b/tests/TestCase/Command/ExportCommandTest.php @@ -0,0 +1,83 @@ +exec(self::$command); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Connection: test'); + $this->assertOutputContains('Driver: Mysql'); + $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` has been exported/'); + + //Exports, with `compression` param + sleep(1); + $this->exec(self::$command . ' --compression bzip2'); + $this->assertExitWithSuccess(); + $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql\.bz2` has been exported/'); + + //Exports, with `filename` param + sleep(1); + $this->exec(self::$command . ' --filename backup.sql'); + $this->assertExitWithSuccess(); + $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup.sql` has been exported/'); + + //Exports, with `rotate` param + sleep(1); + $this->exec(self::$command . ' --rotate 3 -v'); + $this->assertExitWithSuccess(); + $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` has been exported/'); + $this->assertOutputRegExp('/Backup `backup_test_\d+\.sql` has been deleted/'); + $this->assertOutputContains('Deleted backup files: 1'); + + //Exports, with `send` param + sleep(1); + $this->exec(self::$command . ' --send mymail@example.com'); + $this->assertExitWithSuccess(); + $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` has been exported/'); + $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` was sent via mail/'); + } + + /** + * Test for `execute()` method, with an invalid option value + * @test + */ + public function testExecuteInvalidOptionValue() + { + $this->exec(self::$command . ' --filename /noExistingDir/backup.sql'); + $this->assertExitWithError(); + } +} diff --git a/tests/TestCase/Command/ImportCommandTest.php b/tests/TestCase/Command/ImportCommandTest.php new file mode 100644 index 00000000..e6463fbe --- /dev/null +++ b/tests/TestCase/Command/ImportCommandTest.php @@ -0,0 +1,53 @@ +createBackup(); + $this->exec(self::$command . ' ' . $backup); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Connection: test'); + $this->assertOutputContains('Driver: Mysql'); + $this->assertOutputContains('Backup `' . $backup . '` has been imported'); + } + + /** + * Test for `execute()` method, with a no existing filename + * @test + */ + public function testExecuteWithNoExistingFilename() + { + $this->exec(self::$command . ' /noExistingDir/backup.sql'); + $this->assertExitWithError(); + } +} diff --git a/tests/TestCase/Command/IndexCommandTest.php b/tests/TestCase/Command/IndexCommandTest.php new file mode 100644 index 00000000..e0f56367 --- /dev/null +++ b/tests/TestCase/Command/IndexCommandTest.php @@ -0,0 +1,50 @@ +exec(self::$command); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Connection: test'); + $this->assertOutputContains('Driver: Mysql'); + $this->assertOutputContains('Backup files found: 0'); + + $this->createSomeBackups(true); + $this->exec(self::$command); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Backup files found: 3'); + $this->assertOutputRegExp('/backup\.sql\.gz\s+|\s+sql\.gz\s+|\s+gzip\s+|\s+[\d\.]+ \w+\s+|\s+[\d\/]+, [\d:]+ (AP)M/'); + $this->assertOutputRegExp('/backup\.sql\.bz2\s+|\s+sql\.bz2\s+|\s+bzip2\s+|\s+[\d\.]+ \w+\s+|\s+[\d\/]+, [\d:]+ (AP)M/'); + $this->assertOutputRegExp('/backup\.sq\s+|\s+sql\s+|\s+|\s+[\d\.]+ \w+\s+|\s+[\d\/]+, [\d:]+ (AP)M/'); + } +} diff --git a/tests/TestCase/Command/RotateCommandTest.php b/tests/TestCase/Command/RotateCommandTest.php new file mode 100644 index 00000000..83201d00 --- /dev/null +++ b/tests/TestCase/Command/RotateCommandTest.php @@ -0,0 +1,59 @@ +exec(self::$command . ' 1 -v'); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Connection: test'); + $this->assertOutputContains('Driver: Mysql'); + $this->assertOutputContains('No backup has been deleted'); + + $this->createSomeBackups(true); + $this->exec(self::$command . ' 1 -v'); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Backup `backup.sql.bz2` has been deleted'); + $this->assertOutputContains('Backup `backup.sql` has been deleted'); + $this->assertOutputContains('Deleted backup files: 2'); + } + + /** + * Test for `execute()` method, with an invalid value + * @test + */ + public function testExecuteInvalidValue() + { + $this->exec(self::$command . ' string'); + $this->assertExitWithError(); + } +} diff --git a/tests/TestCase/Command/SendCommandTest.php b/tests/TestCase/Command/SendCommandTest.php new file mode 100644 index 00000000..2e0ea72c --- /dev/null +++ b/tests/TestCase/Command/SendCommandTest.php @@ -0,0 +1,65 @@ +createBackup(); + $this->exec(self::$command . ' ' . $file . ' recipient@example.com'); + $this->assertExitWithSuccess(); + $this->assertOutputContains('Connection: test'); + $this->assertOutputContains('Driver: Mysql'); + $this->assertOutputContains('Backup `' . $file . '` was sent via mail'); + } + + /** + * Test for `execute()` method, with a no existing filename + * @test + */ + public function testExecuteWithNoExistingFilename() + { + $this->exec(self::$command . ' /noExistingDir/backup.sql'); + $this->assertExitWithError(); + } + + /** + * Test for `execute()` method, without a sender in the configuration + * @test + */ + public function testExecuteWithoutSenderInConfiguration() + { + Configure::write('DatabaseBackup.mailSender', false); + $this->exec(self::$command . ' file.sql recipient@example.com'); + $this->assertExitWithError(); + } +} diff --git a/tests/TestCase/Shell/BackupShellTest.php b/tests/TestCase/Shell/BackupShellTest.php deleted file mode 100644 index 670c45f2..00000000 --- a/tests/TestCase/Shell/BackupShellTest.php +++ /dev/null @@ -1,262 +0,0 @@ -exec('database_backup.backup index'); - $this->assertOutputContains('Connection: test'); - $this->assertOutputContains('Driver: Mysql'); - } - - /** - * Test for `deleteAll()` method - * @test - */ - public function testDeleteAll() - { - //For now, no backup to be deleted - $this->exec('database_backup.backup delete_all -v'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('No backup has been deleted'); - - //Creates some backups - $this->createSomeBackups(true); - $this->exec('database_backup.backup delete_all -v'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('Backup `backup.sql.gz` has been deleted'); - $this->assertOutputContains('Backup `backup.sql.bz2` has been deleted'); - $this->assertOutputContains('Backup `backup.sql` has been deleted'); - $this->assertOutputContains('Deleted backup files: 3'); - } - - /** - * Test for `export()` method - * @test - */ - public function testExport() - { - $targetRegex = preg_quote(Configure::read(DATABASE_BACKUP . '.target') . DS, '/'); - - //Exports, without params - $this->exec('database_backup.backup export'); - $this->assertExitWithSuccess(); - $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` has been exported/'); - - //Exports, with `compression` param - sleep(1); - $this->exec('database_backup.backup export --compression bzip2'); - $this->assertExitWithSuccess(); - $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql\.bz2` has been exported/'); - - //Exports, with `filename` param - sleep(1); - $this->exec('database_backup.backup export --filename backup.sql'); - $this->assertExitWithSuccess(); - $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup.sql` has been exported/'); - - //Exports, with `rotate` param - sleep(1); - $this->exec('database_backup.backup export --rotate 3 -v'); - $this->assertExitWithSuccess(); - $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` has been exported/'); - $this->assertOutputRegExp('/Backup `backup_test_\d+\.sql` has been deleted/', $this->_out->messages()[4]); - $this->assertOutputContains('Deleted backup files: 1'); - - //Exports, with `send` param - sleep(1); - $this->exec('database_backup.backup export --send mymail@example.com'); - $this->assertExitWithSuccess(); - $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` has been exported/'); - $this->assertOutputRegExp('/Backup `' . $targetRegex . 'backup_test_\d+\.sql` was sent via mail/'); - } - - /** - * Test for `export()` method, with an invalid option value - * @test - */ - public function testExportInvalidOptionValue() - { - $this->exec('database_backup.backup export --filename /noExistingDir/backup.sql'); - $this->assertExitWithError(); - } - - /** - * Test for `index()` method - * @test - */ - public function testIndex() - { - //For now, no backup to index - $this->exec('database_backup.backup index'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('Backup files found: 0'); - - //Creates some backups - $this->createSomeBackups(true); - $this->exec('database_backup.backup index'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('Backup files found: 3'); - - //Checks output about backup files - $this->assertOutputRegExp('/backup\.sql\.gz\s+|\s+sql\.gz\s+|\s+gzip\s+|\s+[\d\.]+ \w+\s+|\s+[\d\/]+, [\d:]+ (AP)M/'); - $this->assertOutputRegExp('/backup\.sql\.bz2\s+|\s+sql\.bz2\s+|\s+bzip2\s+|\s+[\d\.]+ \w+\s+|\s+[\d\/]+, [\d:]+ (AP)M/'); - $this->assertOutputRegExp('/backup\.sq\s+|\s+sql\s+|\s+|\s+[\d\.]+ \w+\s+|\s+[\d\/]+, [\d:]+ (AP)M/'); - } - - /** - * Test for `import()` method - * @test - */ - public function testImport() - { - //Exports a database - $backup = $this->createBackup(); - - $this->exec('database_backup.backup import ' . $backup); - $this->assertExitWithSuccess(); - $this->assertOutputContains('Backup `' . $backup . '` has been imported'); - } - - /** - * Test for `import()` method, with a no existing filename - * @test - */ - public function testImportWithNoExistingFilename() - { - $this->exec('database_backup.backup import /noExistingDir/backup.sql'); - $this->assertExitWithError(); - } - - /** - * Test for `main()` method. As for `index()` with no backups - * @test - */ - public function testMain() - { - $this->exec('database_backup.backup main'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('Backup files found: 0'); - } - - /** - * Test for `rotate()` method - * @test - */ - public function testRotate() - { - //For now, no backup to be deleted - $this->exec('database_backup.backup rotate 1 -v'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('No backup has been deleted'); - - //Creates some backups - $this->createSomeBackups(true); - $this->exec('database_backup.backup rotate 1 -v'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('Backup `backup.sql.bz2` has been deleted'); - $this->assertOutputContains('Backup `backup.sql` has been deleted'); - $this->assertOutputContains('Deleted backup files: 2'); - } - - /** - * Test for `rotate()` method, with an invalid value - * @test - */ - public function testRotateInvalidValue() - { - $this->exec('database_backup.backup rotate string'); - $this->assertExitWithError(); - } - - /** - * Test for `send()` method - * @test - */ - public function testSend() - { - //Gets a backup file - $file = $this->createBackup(); - - $this->exec('database_backup.backup send ' . $file . ' recipient@example.com'); - $this->assertExitWithSuccess(); - $this->assertOutputContains('Backup `' . $file . '` was sent via mail'); - } - - /** - * Test for `send()` method, without a sender in the configuration - * @test - */ - public function testSendWithoutSenderInConfiguration() - { - Configure::write(DATABASE_BACKUP . '.mailSender', false); - - $this->exec('database_backup.backup send file.sql recipient@example.com'); - $this->assertExitWithError(); - } - - /** - * Test for `getOptionParser()` method - * @test - */ - public function testGetOptionParser() - { - $parser = (new BackupShell)->getOptionParser(); - $this->assertInstanceOf('Cake\Console\ConsoleOptionParser', $parser); - $this->assertEquals('Shell to handle database backups', $parser->getDescription()); - $this->assertArrayKeysEqual(['help', 'quiet', 'verbose'], $parser->options()); - - $this->assertArrayKeysEqual([ - 'delete_all', - 'export', - 'import', - 'index', - 'rotate', - 'send', - ], $parser->subcommands()); - - //Checks options for the "export" subcommand - $exportSubcommandOptions = $parser->subcommands()['export']->parser()->options(); - $this->assertEquals('[-c bzip2|gzip]', $exportSubcommandOptions['compression']->usage()); - $this->assertArrayKeysEqual([ - 'compression', - 'filename', - 'help', - 'quiet', - 'rotate', - 'send', - 'verbose', - ], $exportSubcommandOptions); - } -} diff --git a/tests/TestCase/Utility/BackupExportTest.php b/tests/TestCase/Utility/BackupExportTest.php index 505394d9..80339cd0 100644 --- a/tests/TestCase/Utility/BackupExportTest.php +++ b/tests/TestCase/Utility/BackupExportTest.php @@ -33,9 +33,7 @@ class BackupExportTest extends TestCase protected $BackupExport; /** - * Setup the test case, backup the static object values so they can be - * restored. Specifically backs up the contents of Configure and paths in - * App if they have not already been backed up + * Called before every test method * @return void */ public function setUp() @@ -61,15 +59,14 @@ public function setUp() } /** - * Teardown any static object changes and restore them + * Called after every test method * @return void */ public function tearDown() { parent::tearDown(); - //Deletes debug log - safe_unlink(LOGS . 'debug.log'); + safe_unlink_recursive(LOGS, 'empty'); } /** @@ -158,7 +155,7 @@ public function testFilename() /** * Test for `filename()` method, with a file that already exists * @expectedException RuntimeException - * @expectedExceptionMessageRegExp /^File `[\s\w\/:\\]+backup\.sql` already exists$/ + * @expectedExceptionMessageRegExp /^File `[\s\w\/:\\\-]+backup\.sql` already exists$/ */ public function testFilenameAlreadyExists() { @@ -171,7 +168,7 @@ public function testFilenameAlreadyExists() /** * Test for `filename()` method, with a no writable directory * @expectedException ErrorException - * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\]+` is not writable$/ + * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\\-]+` is not writable$/ * @test */ public function testFilenameNotWritableDirectory() @@ -260,7 +257,7 @@ public function testExport() */ public function testExportWithDifferendChmod() { - Configure::write(DATABASE_BACKUP . '.chmod', 0777); + Configure::write('DatabaseBackup.chmod', 0777); $filename = $this->BackupExport->filename('exportWithDifferentChmod.sql')->export(); $this->assertFilePerms($filename, '0777'); } diff --git a/tests/TestCase/Utility/BackupImportTest.php b/tests/TestCase/Utility/BackupImportTest.php index 39ad9471..d397dacc 100644 --- a/tests/TestCase/Utility/BackupImportTest.php +++ b/tests/TestCase/Utility/BackupImportTest.php @@ -37,9 +37,7 @@ class BackupImportTest extends TestCase protected $BackupImport; /** - * Setup the test case, backup the static object values so they can be - * restored. Specifically backs up the contents of Configure and paths in - * App if they have not already been backed up + * Called before every test method * @return void */ public function setUp() @@ -89,7 +87,7 @@ public function testFilename() /** * Test for `filename()` method, with invalid directory * @expectedException ErrorException - * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\]+backup\.sql` is not readable$/ + * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\\-]+backup\.sql` is not readable$/ * @test */ public function testFilenameWithInvalidDirectory() @@ -105,7 +103,7 @@ public function testFilenameWithInvalidDirectory() */ public function testFilenameWithInvalidExtension() { - file_put_contents(Configure::read(DATABASE_BACKUP . '.target') . DS . 'backup.txt', null); + file_put_contents(Configure::read('DatabaseBackup.target') . DS . 'backup.txt', null); $this->BackupImport->filename('backup.txt'); } diff --git a/tests/TestCase/Utility/BackupManagerTest.php b/tests/TestCase/Utility/BackupManagerTest.php index ea5e1e5f..9aa25702 100644 --- a/tests/TestCase/Utility/BackupManagerTest.php +++ b/tests/TestCase/Utility/BackupManagerTest.php @@ -39,9 +39,7 @@ class BackupManagerTest extends TestCase protected $BackupManager; /** - * Setup the test case, backup the static object values so they can be - * restored. Specifically backs up the contents of Configure and paths in - * App if they have not already been backed up + * Called before every test method * @return void */ public function setUp() @@ -79,13 +77,13 @@ public function testDeleteAll() $this->createSomeBackups(true); $this->assertEquals(['backup.sql.gz', 'backup.sql.bz2', 'backup.sql'], $this->BackupManager->deleteAll()); - $this->assertEmpty($this->BackupManager->index()); + $this->assertEmpty($this->BackupManager->index()->toList()); } /** * Test for `delete()` method, with a no existing file * @expectedException ErrorException - * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\]+noExistingFile.sql` is not writable$/ + * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\\-]+noExistingFile.sql` is not writable$/ * @test */ public function testDeleteNoExistingFile() @@ -100,22 +98,22 @@ public function testDeleteNoExistingFile() public function testIndex() { //Creates a text file. This file should be ignored - file_put_contents(Configure::read(DATABASE_BACKUP . '.target') . DS . 'text.txt', null); + file_put_contents(Configure::read('DatabaseBackup.target') . DS . 'text.txt', null); $this->createSomeBackups(true); - $files = collection($this->BackupManager->index()); + $files = $this->BackupManager->index(); //Checks compressions - $compressions = $files->extract('compression')->toArray(); + $compressions = $files->extract('compression')->toList(); $this->assertEquals(['gzip', 'bzip2', false], $compressions); //Checks filenames - $filenames = $files->extract('filename')->toArray(); + $filenames = $files->extract('filename')->toList(); $this->assertEquals(['backup.sql.gz', 'backup.sql.bz2', 'backup.sql'], $filenames); //Checks extensions - $extensions = $files->extract('extension')->toArray(); + $extensions = $files->extract('extension')->toList(); $this->assertEquals(['sql.gz', 'sql.bz2', 'sql'], $extensions); //Checks for properties of each backup object @@ -144,12 +142,11 @@ public function testRotate() //Now there are two files. Only uncompressed file was deleted $filesAfterRotate = $this->BackupManager->index(); - $this->assertEquals(2, count($filesAfterRotate)); - $this->assertEquals('gzip', $filesAfterRotate[0]->compression); - $this->assertEquals('bzip2', $filesAfterRotate[1]->compression); + $this->assertEquals(2, $filesAfterRotate->count()); + $this->assertEquals(['gzip', 'bzip2'], $filesAfterRotate->extract('compression')->toList()); //Gets the difference - $diff = array_udiff($initialFiles, $filesAfterRotate, function ($a, $b) { + $diff = array_udiff($initialFiles->toList(), $filesAfterRotate->toList(), function ($a, $b) { return strcmp($a->filename, $b->filename); }); @@ -185,30 +182,17 @@ public function testSend() $this->_email = $this->invokeMethod($instance, 'getEmailInstance', [$file, $to]); $this->assertInstanceof(Email::class, $this->_email); - $this->assertEmailFrom(Configure::read(DATABASE_BACKUP . '.mailSender')); + $this->assertEmailFrom(Configure::read('DatabaseBackup.mailSender')); $this->assertEmailTo($to); $this->assertEmailSubject('Database backup ' . basename($file) . ' from localhost'); $this->assertEmailAttachmentsContains(basename($file), compact('file', 'mimetype')); $this->assertArrayKeysEqual(['headers', 'message'], $this->BackupManager->send($file, $to)); } - /** - * Test for `send()` method, with empty sender - * @expectedException InvalidArgumentException - * @expectedExceptionMessage The email set for "from" is empty. - * @test - */ - public function testSendEmptySender() - { - Configure::write(DATABASE_BACKUP . '.mailSender', false); - - $this->BackupManager->send($this->createBackup(), 'recipient@example.com'); - } - /** * Test for `send()` method, with an invalid file * @expectedException ErrorException - * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\]+` is not readable$/ + * @expectedExceptionMessageRegExp /^File or directory `[\s\w\/:\\\-]+` is not readable$/ * @test */ public function testSendInvalidFile() @@ -224,7 +208,7 @@ public function testSendInvalidFile() */ public function testSendInvalidSender() { - Configure::write(DATABASE_BACKUP . '.mailSender', 'invalidSender'); + Configure::write('DatabaseBackup.mailSender', 'invalidSender'); $this->BackupManager->send($this->createBackup(), 'recipient@example.com'); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 81727c35..90b5a911 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -33,7 +33,7 @@ define('APP_DIR', 'test_app'); define('WEBROOT_DIR', 'webroot'); define('WWW_ROOT', APP . 'webroot' . DS); -define('TMP', sys_get_temp_dir() . DS); +define('TMP', sys_get_temp_dir() . DS . 'cakephp-database-backup' . DS); define('CONFIG', APP . 'config' . DS); define('CACHE', TMP); define('LOGS', TMP); @@ -114,7 +114,7 @@ $transportName = 'debug'; $transportConfig = ['className' => 'Debug']; -if (class_exists('TransportFactory')) { +if (class_exists(TransportFactory::class)) { TransportFactory::setConfig($transportName, $transportConfig); } else { Email::setConfigTransport($transportName, $transportConfig); diff --git a/tests/test_app/TestApp/Application.php b/tests/test_app/TestApp/Application.php new file mode 100644 index 00000000..49b8b2c4 --- /dev/null +++ b/tests/test_app/TestApp/Application.php @@ -0,0 +1,57 @@ +addPlugin(DatabaseBackup::class); + } + + /** + * Define the HTTP middleware layers for an application + * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to set in your App Class + * @return \Cake\Http\MiddlewareQueue + */ + public function middleware($middlewareQueue) + { + $middlewareQueue->add(new RoutingMiddleware($this)); + + return $middlewareQueue; + } + + /** + * Define the routes for an application + * @param \Cake\Routing\RouteBuilder $routes A route builder to add routes into + * @return void + */ + public function routes($routes) + { + //Do nothing + } +} diff --git a/version b/version index 73462a5a..e70b4523 100644 --- a/version +++ b/version @@ -1 +1 @@ -2.5.1 +2.6.0