diff --git a/.travis.yml b/.travis.yml
index 5ff9af8..9586c1c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,13 +2,12 @@ language: php
sudo: false
php:
- - 5.4
- 5.5
- 5.6
- 7.0
- hhvm
-before_script: composer install --dev --prefer-source
+before_script: composer install --prefer-source
script: phpunit --debug --coverage-text
diff --git a/Client/DownloadableClientInterface.php b/Client/DownloadableClientInterface.php
new file mode 100644
index 0000000..f0d9e01
--- /dev/null
+++ b/Client/DownloadableClientInterface.php
@@ -0,0 +1,12 @@
+restoreFolder = $restoreFolder;
+ $this->localFilesystem = $localFilesystem;
$params = $params['dropbox_sdk'];
$this->access_token = $params['access_token'];
$this->remotePath = $params['remote_path'];
@@ -46,6 +64,37 @@ public function upload($archive)
fclose($fp);
}
+ public function download()
+ {
+ if (!$this->restoreFolder) {
+ throw InvalidConfigurationException::create('$restoreFolder');
+ }
+ $pathError = Dropbox\Path::findErrorNonRoot($this->remotePath);
+ if ($pathError !== null) {
+ throw new UploadException(sprintf('Invalid path "%s".', $this->remotePath));
+ }
+ $client = new Dropbox\Client($this->access_token, 'CloudBackupBundle');
+ $entry = $client->getMetadataWithChildren($this->remotePath);
+ if (!$entry['is_dir']) {
+ throw RestoringNotAvailableException::create();
+ }
+
+ // Fetch the latest file
+ $file = end($entry['contents']);
+ $fileName = substr($file['path'], 1+strrpos($file['path'], '/'));
+ $stream = fopen('php://temp', 'r+');
+ $client->getFile($file['path'], $stream);
+ fseek($stream, 0);
+ $content = stream_get_contents($stream);
+
+ $splFile = new \SplFileInfo($this->restoreFolder . $fileName);
+
+ $this->localFilesystem->dumpFile($splFile->getPathname(), $content);
+
+ return $splFile;
+ }
+
+
/**
* {@inheritdoc}
*/
diff --git a/Client/GaufretteClient.php b/Client/GaufretteClient.php
index 625c5f4..02438da 100644
--- a/Client/GaufretteClient.php
+++ b/Client/GaufretteClient.php
@@ -1,8 +1,9 @@
*/
-class GaufretteClient implements ClientInterface
+class GaufretteClient implements ClientInterface, DownloadableClientInterface
{
+ /**
+ * @var Filesystem[]
+ */
private $filesystems;
+ /**
+ * @var string
+ */
+ private $restoreFolder;
+
+ /**
+ * @var LocalFilesystem
+ */
+ private $localFilesystem;
+
+ /**
+ * @param string $restoreFolder
+ * @param LocalFilesystem $localFilesystem
+ */
+ public function __construct($restoreFolder = null, LocalFilesystem $localFilesystem = null)
+ {
+ $this->restoreFolder = $restoreFolder;
+ $this->localFilesystem = $localFilesystem;
+ }
+
/**
* {@inheritdoc}
*/
@@ -35,6 +59,14 @@ public function addFilesystem(Filesystem $filesystem)
$this->filesystems[] = $filesystem;
}
+ /**
+ * @return Filesystem
+ */
+ private function getFirstFilesystem()
+ {
+ return $this->filesystems[0];
+ }
+
/**
* {@inheritdoc}
*/
@@ -42,4 +74,25 @@ public function getName()
{
return 'Gaufrette';
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function download()
+ {
+ if (!$this->restoreFolder) {
+ throw InvalidConfigurationException::create('$restoreFolder');
+ }
+ $fileSystem = $this->getFirstFilesystem();
+
+ $files = $fileSystem->keys();
+ $fileName = end($files);
+
+ $content = $fileSystem->get($fileName)->getContent();
+ $splFile = new \SplFileInfo($this->restoreFolder . $fileName);
+
+ $this->localFilesystem->dumpFile($splFile->getPathname(), $content);
+
+ return $splFile;
+ }
}
diff --git a/Command/RestoreCommand.php b/Command/RestoreCommand.php
new file mode 100644
index 0000000..81a49e5
--- /dev/null
+++ b/Command/RestoreCommand.php
@@ -0,0 +1,84 @@
+doRestore = $doRestore;
+ $this->restoreManager = $restoreManager;
+
+ parent::__construct();
+ }
+
+ /**
+ * Configure the command.
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('dizda:backup:restore')
+ ->setDescription('Download latest backup, uncompress and restore.')
+ ->addOption('force', null, InputOption::VALUE_NONE)
+ ;
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ *
+ * @return int
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ if (!$this->doRestore) {
+ $output->writeln('Restore is not available. Enable by setting dizda_cloud_backup.restore: true in config.yml.');
+
+ return self::RETURN_STATUS_NOT_AVAILABLE;
+ }
+
+ if (!$input->getOption('force')) {
+ $output->writeln('Run command with --force to execute.');
+
+ return self::RETURN_STATUS_MISSING_FORCE_FLAG;
+ }
+
+ $output->writeln('Restoring backup started');
+
+ if ($this->restoreManager->execute()) {
+ $output->writeln('Restoring backup completed');
+
+ return self::RETURN_STATUS_SUCCESS;
+ } else {
+ $output->writeln('Something went wrong');
+
+ return self::RETURN_STATUS_EXCEPTION_OCCURRED;
+ }
+ }
+}
diff --git a/Database/MySQL.php b/Database/MySQL.php
index f048497..7b49a74 100644
--- a/Database/MySQL.php
+++ b/Database/MySQL.php
@@ -1,6 +1,7 @@
*/
-class MySQL extends BaseDatabase
+class MySQL extends BaseDatabase implements RestorableDatabaseInterface
{
const DB_PATH = 'mysql';
const CONFIGURATION_FILE_NAME = 'mysql.cnf';
@@ -20,14 +21,20 @@ class MySQL extends BaseDatabase
private $params;
/**
- * DB Auth.
- *
+ * @var string
+ */
+ private $restoreFolder;
+
+ /**
* @param array $params
* @param string $basePath
+ * @param string $restoreFolder
*/
- public function __construct($params, $basePath)
+ public function __construct($params, $basePath, $restoreFolder = null)
{
parent::__construct($basePath);
+
+ $this->restoreFolder = $restoreFolder;
$this->params = $params['mysql'];
}
@@ -135,6 +142,14 @@ public function dump()
$this->removeConfigurationFile();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function restore()
+ {
+ $this->execute($this->getRestoreCommand());
+ }
+
/**
* {@inheritdoc}
*/
@@ -148,6 +163,35 @@ protected function getCommand()
);
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getRestoreCommand()
+ {
+ if (!$this->restoreFolder) {
+ throw InvalidConfigurationException::create('$restoreFolder');
+ }
+
+ $restoreAuth = '';
+ if ($this->params['db_user']) {
+ $restoreAuth = sprintf('-u%s', $this->params['db_user']);
+
+ if ($this->params['db_password']) {
+ $restoreAuth = $restoreAuth . sprintf(" --password=\"%s\"", $this->params['db_password']);
+ }
+ }
+
+ $this->prepareFileName();
+
+ $command = sprintf('mysql %s %s < %s',
+ $restoreAuth,
+ $this->params['database'],
+ ProcessUtils::escapeArgument(sprintf('%smysql/%s', $this->restoreFolder, $this->fileName))
+ );
+
+ return $command;
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/Database/RestorableDatabaseInterface.php b/Database/RestorableDatabaseInterface.php
new file mode 100644
index 0000000..e732a8c
--- /dev/null
+++ b/Database/RestorableDatabaseInterface.php
@@ -0,0 +1,11 @@
+end()
->end()
->end()
+ ->booleanNode('restore')->defaultFalse()->end()
->end();
return $treeBuilder;
diff --git a/DependencyInjection/DizdaCloudBackupExtension.php b/DependencyInjection/DizdaCloudBackupExtension.php
index f01d6de..558242c 100644
--- a/DependencyInjection/DizdaCloudBackupExtension.php
+++ b/DependencyInjection/DizdaCloudBackupExtension.php
@@ -29,6 +29,7 @@ public function load(array $configs, ContainerBuilder $container)
/* Config output file */
$container->setParameter('dizda_cloud_backup.root_folder', $container->getParameter('kernel.root_dir').'/../');
$container->setParameter('dizda_cloud_backup.output_folder', $container->getParameter('kernel.cache_dir').'/backup/');
+ $container->setParameter('dizda_cloud_backup.restore_folder', $container->getParameter('kernel.cache_dir').'/restore/');
/* Assign all config vars */
foreach ($config as $k => $v) {
diff --git a/Event/RestoreEvent.php b/Event/RestoreEvent.php
new file mode 100644
index 0000000..50c61b0
--- /dev/null
+++ b/Event/RestoreEvent.php
@@ -0,0 +1,31 @@
+file = $file;
+ }
+
+ /**
+ * @return \SplFileInfo
+ */
+ public function getFile()
+ {
+ return $this->file;
+ }
+}
diff --git a/Event/RestoreFailedEvent.php b/Event/RestoreFailedEvent.php
new file mode 100644
index 0000000..5ff3746
--- /dev/null
+++ b/Event/RestoreFailedEvent.php
@@ -0,0 +1,31 @@
+exception = $exception;
+ }
+
+ /**
+ * @return \Exception
+ */
+ public function getException()
+ {
+ return $this->exception;
+ }
+}
diff --git a/Exception/InvalidConfigurationException.php b/Exception/InvalidConfigurationException.php
new file mode 100644
index 0000000..fdd2353
--- /dev/null
+++ b/Exception/InvalidConfigurationException.php
@@ -0,0 +1,13 @@
+restoreFolder = $restoreFolder;
+ $this->filesystem = $filesystem;
+ }
+
+ /**
+ * @param RestoreEvent $event
+ */
+ public function whenRestoreIsCompleted(RestoreEvent $event)
+ {
+ $this->clean();
+ }
+
+ /**
+ * @param RestoreFailedEvent $event
+ */
+ public function whenRestoreIsFailed(RestoreFailedEvent $event)
+ {
+ $this->clean();
+ }
+
+ private function clean()
+ {
+ $this->filesystem->remove($this->restoreFolder);
+ }
+}
diff --git a/Listener/LogRestoreCompletedListener.php b/Listener/LogRestoreCompletedListener.php
new file mode 100644
index 0000000..0e07387
--- /dev/null
+++ b/Listener/LogRestoreCompletedListener.php
@@ -0,0 +1,29 @@
+logger = $logger;
+ }
+
+ /**
+ * @param RestoreEvent $event
+ */
+ public function whenRestoreIsCompleted(RestoreEvent $event)
+ {
+ $this->logger->info(sprintf('[dizda-backup] Restoring %s is completed', $event->getFile()->getFilename()));
+ }
+}
diff --git a/Listener/LogRestoreFailedListener.php b/Listener/LogRestoreFailedListener.php
new file mode 100644
index 0000000..a987fe6
--- /dev/null
+++ b/Listener/LogRestoreFailedListener.php
@@ -0,0 +1,29 @@
+logger = $logger;
+ }
+
+ /**
+ * @param RestoreFailedEvent $event
+ */
+ public function whenRestoreIsFailed(RestoreFailedEvent $event)
+ {
+ $this->logger->critical('[dizda-backup] Restoring database failed', ['exception' => $event->getException()]);
+ }
+}
diff --git a/Manager/ClientManager.php b/Manager/ClientManager.php
index 12fb94f..e8fcb1c 100644
--- a/Manager/ClientManager.php
+++ b/Manager/ClientManager.php
@@ -3,6 +3,8 @@
namespace Dizda\CloudBackupBundle\Manager;
use Dizda\CloudBackupBundle\Client\ClientInterface;
+use Dizda\CloudBackupBundle\Client\DownloadableClientInterface;
+use Dizda\CloudBackupBundle\Exception\MissingDownloadableClientsException;
use Psr\Log\LoggerInterface;
/**
@@ -70,4 +72,20 @@ public function upload($files)
throw $exception;
}
}
+
+ /**
+ * @return \SplFileInfo
+ *
+ * @throws MissingDownloadableClientsException
+ */
+ public function download()
+ {
+ foreach ($this->children as $child) {
+ if ($child instanceof DownloadableClientInterface) {
+ return $child->download();
+ }
+ }
+
+ throw MissingDownloadableClientsException::create();
+ }
}
diff --git a/Manager/DatabaseManager.php b/Manager/DatabaseManager.php
index 697c281..de7afbb 100644
--- a/Manager/DatabaseManager.php
+++ b/Manager/DatabaseManager.php
@@ -3,6 +3,8 @@
namespace Dizda\CloudBackupBundle\Manager;
use Dizda\CloudBackupBundle\Database\DatabaseInterface;
+use Dizda\CloudBackupBundle\Database\RestorableDatabaseInterface;
+use Dizda\CloudBackupBundle\Exception\MissingRestorableDatabaseException;
use Psr\Log\LoggerInterface;
/**
@@ -52,4 +54,19 @@ public function dump()
$child->dump();
}
}
+
+ /**
+ * @throws MissingRestorableDatabaseException
+ */
+ public function restore()
+ {
+ foreach ($this->children as $child) {
+ if ($child instanceof RestorableDatabaseInterface) {
+ $child->restore();
+ return;
+ }
+ }
+
+ throw MissingRestorableDatabaseException::create();
+ }
}
diff --git a/Manager/ProcessorManager.php b/Manager/ProcessorManager.php
index 929a8a8..f7ca969 100644
--- a/Manager/ProcessorManager.php
+++ b/Manager/ProcessorManager.php
@@ -2,7 +2,9 @@
namespace Dizda\CloudBackupBundle\Manager;
+use Dizda\CloudBackupBundle\Exception\UncompressionNotSupportedException;
use Dizda\CloudBackupBundle\Processor\ProcessorInterface;
+use Dizda\CloudBackupBundle\Processor\UncompressableProcessorInterface;
use Dizda\CloudBackupBundle\Splitter\BaseSplitter;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\Process;
@@ -66,14 +68,20 @@ class ProcessorManager
*/
protected $splitter;
+ /**
+ * @var string
+ */
+ protected $restoreFolder;
+
/**
* @param string $rootPath Path to root folder
* @param string $outputPath Path to folder with archived files
* @param string $filePrefix Prefix for archive file (e.g. sitename)
* @param array $properties Date function format
* @param array $folders Array of folders to archive (relative to $rootPath)
+ * @param string $restoreFolder Path to restore folder
*/
- public function __construct($rootPath, $outputPath, $filePrefix, $properties, array $folders = array())
+ public function __construct($rootPath, $outputPath, $filePrefix, $properties, array $folders = array(), $restoreFolder = null)
{
$this->rootPath = $rootPath;
$this->outputPath = $outputPath;
@@ -81,6 +89,7 @@ public function __construct($rootPath, $outputPath, $filePrefix, $properties, ar
$this->folders = $folders;
$this->properties = $properties;
$this->compressedArchivePath = $this->outputPath.'../backup_compressed/';
+ $this->restoreFolder = $restoreFolder;
$this->filesystem = new Filesystem();
}
@@ -126,6 +135,22 @@ public function compress()
}
}
+ /**
+ * @param \SplFileInfo $file
+ */
+ public function uncompress(\SplFileInfo $file)
+ {
+ if (!$this->processor instanceof UncompressableProcessorInterface) {
+ throw new UncompressionNotSupportedException(
+ sprintf('Uncompression is not supported for %s.', $this->processor->getName())
+ );
+ }
+
+ $this->archivePath = $this->compressedArchivePath . $this->buildArchiveFilename();
+ $archive = $this->processor->getUncompressCommand($this->restoreFolder, $file->getPathname(), $this->restoreFolder);
+ $this->execute($archive);
+ }
+
/**
* Return the archive file name.
*
diff --git a/Manager/RestoreManager.php b/Manager/RestoreManager.php
new file mode 100644
index 0000000..b13a70d
--- /dev/null
+++ b/Manager/RestoreManager.php
@@ -0,0 +1,97 @@
+databaseManager = $databaseManager;
+ $this->clientManager = $clientManager;
+ $this->processorManager = $processorManager;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->restoreFolder = $restoreFolder;
+ $this->filesystem = $filesystem;
+ $this->doRestore = (boolean) $doRestore;
+ }
+
+ /**
+ * @return bool
+ */
+ public function execute()
+ {
+ if (!$this->doRestore) {
+ throw RestoringNotAvailableException::create();
+ }
+
+ try {
+ $this->filesystem->mkdir($this->restoreFolder);
+ $file = $this->clientManager->download();
+ $this->processorManager->uncompress($file);
+ $this->databaseManager->restore();
+ $this->eventDispatcher->dispatch(RestoreEvent::RESTORE_COMPLETED, new RestoreEvent($file));
+
+ return true;
+ } catch (\Exception $e) {
+ $this->eventDispatcher->dispatch(RestoreFailedEvent::RESTORE_FAILED, new RestoreFailedEvent($e));
+ }
+
+ return false;
+ }
+}
diff --git a/Processor/UncompressableProcessorInterface.php b/Processor/UncompressableProcessorInterface.php
new file mode 100644
index 0000000..c29acf0
--- /dev/null
+++ b/Processor/UncompressableProcessorInterface.php
@@ -0,0 +1,16 @@
+getMock(LocalFilesystem::class);
+ $localFilesystemMock->expects($this->once())->method('dumpFile')
+ ->with('/tmp/restore/db_2016-10-19.zip', 'foo bar');
+ $client = new GaufretteClient('/tmp/restore/', $localFilesystemMock);
+ $fileMock = $this
+ ->getMockBuilder(File::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getContent'])
+ ->getMock()
+ ;
+ $fileMock->method('getContent')->willReturn('foo bar');
+ $fileSystemMock = $this
+ ->getMockBuilder(Filesystem::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['keys', 'get'])
+ ->getMock();
+ $fileSystemMock->method('keys')->willReturn(['db_2016-10-19.zip']);
+ $fileSystemMock->method('get')->with('db_2016-10-19.zip')->willReturn($fileMock);
+ $client->addFilesystem($fileSystemMock);
+
+ $file = $client->download();
+
+ $this->assertInstanceOf('\SplFileInfo', $file);
+ $this->assertEquals('/tmp/restore/db_2016-10-19.zip', $file->getPathname());
+ }
+
+ /**
+ * @test
+ * @expectedException \Dizda\CloudBackupBundle\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage Parameter "$restoreFolder" is not set.
+ */
+ public function throwExceptionIfRestoreFolderIsNotConfigured()
+ {
+ $client = new GaufretteClient(null, $this->getMock(LocalFilesystem::class));
+ $client->download();
+ }
+}
diff --git a/Tests/Database/MySQLTest.php b/Tests/Database/MySQLTest.php
index a93f209..cedf35e 100644
--- a/Tests/Database/MySQLTest.php
+++ b/Tests/Database/MySQLTest.php
@@ -36,7 +36,7 @@ public function shouldDumpAllDatabases()
'db_user' => 'root',
'db_password' => 'test',
),
- ), '/tmp/backup/');
+ ));
$this->assertEquals($mysql->getCommand(), "mysqldump --defaults-extra-file=\"/tmp/backup/mysql/mysql.cnf\" --all-databases > '/tmp/backup/mysql/all-databases.sql'");
$this->checkConfigurationFileExistsAndValid('root', 'test', 'localhost', '3306');
@@ -56,7 +56,7 @@ public function shouldDumpSpecifiedDatabase()
'db_user' => 'root',
'db_password' => 'test',
),
- ), '/tmp/backup/');
+ ));
$this->assertEquals($mysql1->getCommand(), "mysqldump --defaults-extra-file=\"/tmp/backup/mysql/mysql.cnf\" dizbdd > '/tmp/backup/mysql/dizbdd.sql'");
$this->checkConfigurationFileExistsAndValid('root', 'test', 'localhost', '3306');
@@ -70,7 +70,7 @@ public function shouldDumpSpecifiedDatabase()
'db_user' => 'mysql',
'db_password' => 'somepwd',
),
- ), '/tmp/backup/');
+ ));
$this->assertEquals($mysql2->getCommand(), "mysqldump --defaults-extra-file=\"/tmp/backup/mysql/mysql.cnf\" somebdd > '/tmp/backup/mysql/somebdd.sql'");
$this->checkConfigurationFileExistsAndValid('mysql', 'somepwd', 'somehost', '2222');
@@ -85,7 +85,7 @@ public function shouldDumpSpecifiedDatabase()
'db_user' => null,
'db_password' => null,
),
- ), '/tmp/backup/');
+ ));
$this->assertEquals($mysql->getCommand(), 'mysqldump --defaults-extra-file="/tmp/backup/mysql/mysql.cnf" somebdd > \'/tmp/backup/mysql/somebdd.sql\'');
$this->checkConfigurationFileExistsAndValid(null, null, 'somehost', '2222');
@@ -106,7 +106,7 @@ public function shouldDumpAllDatabasesWithNoAuth()
'db_user' => null,
'db_password' => null,
),
- ), '/tmp/backup/');
+ ));
$this->assertEquals($mysql->getCommand(), 'mysqldump --defaults-extra-file="/tmp/backup/mysql/mysql.cnf" --all-databases > \'/tmp/backup/mysql/all-databases.sql\'');
$this->checkConfigurationFileExistsAndValid(null, null, 'somehost', '2222');
@@ -127,7 +127,7 @@ public function shouldIgnoreSpecifiedTablesForSpecifiedDatabase()
'db_password' => 'test',
'ignore_tables' => array('table1', 'table2'),
),
- ), '/tmp/backup/');
+ ));
$this->assertEquals($mysql->getCommand(), "mysqldump --defaults-extra-file=\"/tmp/backup/mysql/mysql.cnf\" dizbdd --ignore-table=dizbdd.table1 --ignore-table=dizbdd.table2 > '/tmp/backup/mysql/dizbdd.sql'");
$this->checkConfigurationFileExistsAndValid('root', 'test', 'localhost', '3306');
@@ -148,7 +148,7 @@ public function shouldIgnoreSpecifiedTablesForAllDatabase()
'db_password' => 'test',
'ignore_tables' => array('db1.table1', 'db2.table2'),
),
- ), '/tmp/backup/');
+ ));
$this->assertEquals($mysql->getCommand(), "mysqldump --defaults-extra-file=\"/tmp/backup/mysql/mysql.cnf\" --all-databases --ignore-table=db1.table1 --ignore-table=db2.table2 > '/tmp/backup/mysql/all-databases.sql'");
$this->checkConfigurationFileExistsAndValid('root', 'test', 'localhost', '3306');
@@ -170,17 +170,86 @@ public function shouldThrowExceptionIfDatabaseIsNotSpecifiedForIgnoredTableDumpi
'db_password' => 'test',
'ignore_tables' => array('table1'),
),
- ), '/tmp/backup/');
+ ));
$mysql->getCommand();
}
+
+ /**
+ * @test
+ */
+ public function shouldReturnRestoreCommand()
+ {
+ $mysql = new MySQLDummy([
+ 'mysql' => [
+ 'all_databases' => false,
+ 'db_host' => 'localhost',
+ 'db_port' => 3306,
+ 'database' => 'dizbdd',
+ 'db_user' => 'root',
+ 'db_password' => null,
+ ],
+ ]);
+
+ $this->assertEquals('mysql -uroot dizbdd < \'/tmp/restore/mysql/dizbdd.sql\'', $mysql->getRestoreCommand());
+ }
+
+ /**
+ * @test
+ */
+ public function shouldReturnRestoreCommandWithPassword()
+ {
+ $mysql = new MySQLDummy([
+ 'mysql' => [
+ 'all_databases' => false,
+ 'db_host' => 'localhost',
+ 'db_port' => 3306,
+ 'database' => 'dizbdd',
+ 'db_user' => 'root',
+ 'db_password' => 'foobar',
+ ],
+ ]);
+
+ $this->assertEquals('mysql -uroot --password="foobar" dizbdd < \'/tmp/restore/mysql/dizbdd.sql\'', $mysql->getRestoreCommand());
+ }
+
+ /**
+ * @test
+ * @expectedException \Dizda\CloudBackupBundle\Exception\InvalidConfigurationException
+ * @expectedExceptionMessage Parameter "$restoreFolder" is not set.
+ */
+ public function throwExceptionIfRestoreFolderIsNotConfigured()
+ {
+ $mysql = new MySQLDummy([
+ 'mysql' => [
+ 'all_databases' => false,
+ 'db_host' => 'localhost',
+ 'db_port' => 3306,
+ 'database' => 'dizbdd',
+ 'db_user' => 'root',
+ 'db_password' => 'foobar',
+ ],
+ ], '/tmp/backup/', null);
+
+ $mysql->getRestoreCommand();
+ }
}
class MySQLDummy extends MySQL
{
+ public function __construct(array $params, $basePath = '/tmp/backup/', $restoreFolder = '/tmp/restore/')
+ {
+ parent::__construct($params, $basePath, $restoreFolder);
+ }
+
public function getCommand()
{
$this->prepareEnvironment();
return parent::getCommand();
}
+
+ public function getRestoreCommand()
+ {
+ return parent::getRestoreCommand();
+ }
}
diff --git a/Tests/Manager/ClientManagerTest.php b/Tests/Manager/ClientManagerTest.php
new file mode 100644
index 0000000..6386ec3
--- /dev/null
+++ b/Tests/Manager/ClientManagerTest.php
@@ -0,0 +1,44 @@
+getMock(ClientInterface::class);
+ $clientMock = $this->getMock(DownloadableClientInterface::class);
+ $fileMock = $this
+ ->getMockBuilder(\SplFileInfo::class)
+ ->setConstructorArgs([tempnam(sys_get_temp_dir(), '')])
+ ->getMock();
+ $clientMock->expects($this->once())->method('download')->willReturn($fileMock);
+ $clients[] = $clientMock;
+
+ $clientManager = new ClientManager($this->getMock(LoggerInterface::class), $clients);
+ $this->assertSame($fileMock, $clientManager->download());
+ }
+
+ /**
+ * @test
+ * @expectedException \Dizda\CloudBackupBundle\Exception\MissingDownloadableClientsException
+ * @expectedExceptionMessage No downloadable client is registered.
+ */
+ public function shouldThrowExceptionIfNoChildIsADownloadableClient()
+ {
+ $clients = [];
+ $clients[] = $this->getMock(ClientInterface::class);
+ $clients[] = $this->getMock(ClientInterface::class);
+
+ $clientManager = new ClientManager($this->getMock(LoggerInterface::class), $clients);
+ $clientManager->download();
+ }
+}
diff --git a/Tests/Manager/RestoreManagerTest.php b/Tests/Manager/RestoreManagerTest.php
new file mode 100644
index 0000000..6a1a5e4
--- /dev/null
+++ b/Tests/Manager/RestoreManagerTest.php
@@ -0,0 +1,111 @@
+getMockBuilder(\SplFileInfo::class)
+ ->setConstructorArgs([tempnam(sys_get_temp_dir(), '')])
+ ->getMock();
+
+ $databaseManagerMock = $this->getMockBuilder(DatabaseManager::class)->disableOriginalConstructor()->getMock();
+ $databaseManagerMock->expects($this->once())->method('restore');
+ $clientManagerMock = $this->getMockBuilder(ClientManager::class)->disableOriginalConstructor()->getMock();
+ $clientManagerMock->expects($this->once())->method('download')->willReturn($fileMock);
+ $processorManagerMock = $this->getMockBuilder(ProcessorManager::class)->disableOriginalConstructor()->getMock();
+ $processorManagerMock->expects($this->once())->method('uncompress');
+ $eventDispatcherMock = $this->getMock(EventDispatcherInterface::class);
+ $eventDispatcherMock->expects($this->once())->method('dispatch')->with(RestoreEvent::RESTORE_COMPLETED);
+ $filesystemMock = $this->getMock(Filesystem::class);
+
+ $restoreManager = new RestoreManager(
+ $databaseManagerMock,
+ $clientManagerMock,
+ $processorManagerMock,
+ $eventDispatcherMock,
+ '',
+ $filesystemMock,
+ true
+ );
+
+ $restoreManager->execute();
+ }
+
+ /**
+ * @test
+ * @expectedException \Dizda\CloudBackupBundle\Exception\RestoringNotAvailableException
+ * @expectedExceptionMessage Restoring is not available.
+ */
+ public function shouldNotRestoreDatabase()
+ {
+ $databaseManagerMock = $this->getMockBuilder(DatabaseManager::class)->disableOriginalConstructor()->getMock();
+ $databaseManagerMock->expects($this->never())->method('restore');
+ $clientManagerMock = $this->getMockBuilder(ClientManager::class)->disableOriginalConstructor()->getMock();
+ $clientManagerMock->expects($this->never())->method('download');
+ $processorManagerMock = $this->getMockBuilder(ProcessorManager::class)->disableOriginalConstructor()->getMock();
+ $processorManagerMock->expects($this->never())->method('uncompress');
+ $eventDispatcherMock = $this->getMock(EventDispatcherInterface::class);
+ $eventDispatcherMock->expects($this->never())->method('dispatch');
+ $filesystemMock = $this->getMock(Filesystem::class);
+
+ $restoreManager = new RestoreManager(
+ $databaseManagerMock,
+ $clientManagerMock,
+ $processorManagerMock,
+ $eventDispatcherMock,
+ '',
+ $filesystemMock,
+ false
+ );
+
+ $restoreManager->execute();
+ }
+
+ /**
+ * @test
+ */
+ public function shouldDispachRestoreFailedEventIfExceptionOccur()
+ {
+ $databaseManagerMock = $this->getMockBuilder(DatabaseManager::class)->disableOriginalConstructor()->getMock();
+ $databaseManagerMock->expects($this->never())->method('restore');
+ $clientManagerMock = $this->getMockBuilder(ClientManager::class)->disableOriginalConstructor()->getMock();
+ $clientManagerMock->expects($this->never())->method('download');
+ $processorManagerMock = $this->getMockBuilder(ProcessorManager::class)->disableOriginalConstructor()->getMock();
+ $processorManagerMock->expects($this->never())->method('uncompress');
+ $eventDispatcherMock = $this->getMock(EventDispatcherInterface::class);
+ $eventDispatcherMock->expects($this->any())->method('dispatch')->with(
+ new \PHPUnit_Framework_Constraint_Not(RestoreEvent::RESTORE_COMPLETED)
+ );
+ $eventDispatcherMock->expects($this->once())->method('dispatch')->with(RestoreFailedEvent::RESTORE_FAILED);
+ $filesystemMock = $this->getMock(Filesystem::class);
+ $filesystemMock->expects($this->once())->method('mkdir')->will($this->throwException(new \Exception()));
+
+ $restoreManager = new RestoreManager(
+ $databaseManagerMock,
+ $clientManagerMock,
+ $processorManagerMock,
+ $eventDispatcherMock,
+ '',
+ $filesystemMock,
+ true
+ );
+
+ $restoreManager->execute();
+ }
+}
diff --git a/composer.json b/composer.json
index 71534f3..d2949f5 100644
--- a/composer.json
+++ b/composer.json
@@ -19,8 +19,8 @@
],
"require": {
- "php": "^5.4 || ^7.0",
- "symfony/framework-bundle": "^2.1 || ^3.0",
+ "php": "^5.5 || ^7.0",
+ "symfony/framework-bundle": "^2.3 || ^3.0",
"psr/log": "^1.0.1"
},