diff --git a/README.md b/README.md index 770685b4..fd42d401 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ If you are behind php 7.0 you can still use phpbu version [4.0.10](https://phar. + Dropbox + FTP + Google Drive + + Google Cloud Storage + OpenStack + rsync + SFTP diff --git a/composer.json b/composer.json index d4f200a9..7d840a28 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,8 @@ "php-opencloud/openstack": "^3.0", "arhitector/yandex": "^2.0", "microsoft/azure-storage-blob": "^1.4", - "phpmailer/phpmailer": "^6.0" + "phpmailer/phpmailer": "^6.0", + "google/cloud-storage": "^1.42" }, "suggest": { "sebastianfeldmann/ftp": "Require ^0.9.2 to sync to an FTP server", @@ -79,7 +80,8 @@ "google/apiclient":"Require ^2.0 to sync to Google Drive", "arhitector/yandex":"Require ^2.0 to sync to Yandex Disk", "microsoft/azure-storage-blob": "Require ^1.4 to sync to Azure Blob Storage", - "phpmailer/phpmailer": "Require ^6.0 to receive logs via email" + "phpmailer/phpmailer": "Require ^6.0 to receive logs via email", + "google/cloud-storage": "Require ^1.42 to sync to Google Cloud Storage" }, "bin": [ "phpbu" diff --git a/doc/config/sync/googlecloudstorage.json b/doc/config/sync/googlecloudstorage.json new file mode 100644 index 00000000..eccf90cc --- /dev/null +++ b/doc/config/sync/googlecloudstorage.json @@ -0,0 +1,8 @@ +{ + "type": "googlecloudstorage", + "options": { + "secret": "backup/google/client_secret.json", + "bucket": "my-bucket", + "path": "remote/path" + } +} diff --git a/doc/config/sync/googlecloudstorage.xml b/doc/config/sync/googlecloudstorage.xml new file mode 100644 index 00000000..db872baf --- /dev/null +++ b/doc/config/sync/googlecloudstorage.xml @@ -0,0 +1,15 @@ + + + + diff --git a/phpbu.schema.json b/phpbu.schema.json index dbe5cbcf..6edf251f 100644 --- a/phpbu.schema.json +++ b/phpbu.schema.json @@ -278,6 +278,7 @@ "Dropbox", "Ftp", "GoogleDrive", + "GoogleCloudStorage", "RSync", "Sftp", "Softlayer", diff --git a/src/Backup/Collector/GoogleCloudStorage.php b/src/Backup/Collector/GoogleCloudStorage.php new file mode 100644 index 00000000..151e0738 --- /dev/null +++ b/src/Backup/Collector/GoogleCloudStorage.php @@ -0,0 +1,58 @@ + + * @copyright Sebastian Feldmann + * @license https://opensource.org/licenses/MIT The MIT License (MIT) + * @link http://phpbu.de/ + */ +class GoogleCloudStorage extends Remote implements Collector +{ + /** + * Bucket to read from. + * + * @var Bucket + */ + private Bucket $bucket; + + /** + * @param Target $target + * @param Bucket $bucket + * @param Path $path + */ + public function __construct(Target $target, Bucket $bucket, Path $path) + { + $this->setUp($target, $path); + $this->bucket = $bucket; + } + + /** + * Collect all created backups. + * + * @return void + */ + protected function collectBackups() + { + /** @var StorageObject $object */ + foreach ($this->bucket->objects() as $object) { + if ($this->isFileMatch($this->path->getPath() . '/' . $object->name())) { + $file = new File\GoogleCloudStorage($object); + + $this->files[$this->getFileIndex($file)] = $file; + } + } + } +} diff --git a/src/Backup/File/GoogleCloudStorage.php b/src/Backup/File/GoogleCloudStorage.php new file mode 100644 index 00000000..13ecb762 --- /dev/null +++ b/src/Backup/File/GoogleCloudStorage.php @@ -0,0 +1,54 @@ + + * @copyright Sebastian Feldmann + * @license https://opensource.org/licenses/MIT The MIT License (MIT) + * @link http://phpbu.de/ + */ +class GoogleCloudStorage extends Remote +{ + /** + * Google Storage Object + * + * @var StorageObject + */ + private StorageObject $object; + + /** + * Constructor. + * + * @param StorageObject $googleStorageObject + */ + public function __construct(StorageObject $googleStorageObject) + { + $this->object = $googleStorageObject; + $this->filename = $this->object->name(); + $this->pathname = $this->object->name(); + $this->size = (int)$this->object->info()['size']; + $this->lastModified = strtotime($this->object->info()['updated']); + } + + /** + * Deletes the file from Google Cloud Storage. + * + * @throws Exception + */ + public function unlink() + { + try { + $this->object->delete(); + } catch (\Exception $e) { + throw new Exception($e->getMessage()); + } + } +} diff --git a/src/Backup/Sync/GoogleCloudStorage.php b/src/Backup/Sync/GoogleCloudStorage.php new file mode 100644 index 00000000..2f7072e3 --- /dev/null +++ b/src/Backup/Sync/GoogleCloudStorage.php @@ -0,0 +1,173 @@ + + * @copyright Sebastian Feldmann + * @license https://opensource.org/licenses/MIT The MIT License (MIT) + * @link http://phpbu.de/ + */ +class GoogleCloudStorage implements Simulator +{ + use Cleanable; + + /** + * Google Gloud Storage client. + * + * @var StorageClient + */ + private $client; + + /** + * Google json secret file. + * + * @var string + */ + private $secret; + + /** + * Bucket to upload to + * + * @var string + */ + private $bucket; + + /** + * Path to upload to in the bucket + * + * @var string + */ + private $parent; + + /** + * (non-PHPDoc) + * + * @param array $options + * @throws \phpbu\App\Exception + * @see \phpbu\App\Backup\Sync::setup() + */ + public function setup(array $options) + { + if (!class_exists('\\Google\\Cloud\\Storage\\StorageClient')) { + throw new Exception('google cloud api client not loaded: use composer to install "google/cloud-storage"'); + } + if (!Util\Arr::isSetAndNotEmptyString($options, 'secret')) { + throw new Exception('google secret json file is mandatory'); + } + if (!Util\Arr::isSetAndNotEmptyString($options, 'bucket')) { + throw new Exception('bucket to upload to is mandatory'); + } + $this->parent = Util\Arr::getValue($options, 'path', ''); + + $this->setupAuthFiles($options); + $this->setUpCleanable($options); + } + + /** + * Make sure google authentication files exist and determine absolute path to them. + * + * @param array $config + * @throws \phpbu\App\Backup\Sync\Exception + */ + private function setupAuthFiles(array $config) + { + $secret = Util\Path::toAbsolutePath($config['secret'], Configuration::getWorkingDirectory()); + if (!file_exists($secret)) { + throw new Exception(sprintf('google secret json file not found at %s', $secret)); + } + + $this->secret = $secret; + $this->bucket = $config['bucket']; + } + + /** + * Execute the Sync + * + * @param \phpbu\App\Backup\Target $target + * @param \phpbu\App\Result $result + * @throws \phpbu\App\Backup\Sync\Exception + * @see \phpbu\App\Backup\Sync::sync() + */ + public function sync(Target $target, Result $result) + { + try { + $this->client = $this->createGoogleCloudClient(); + $bucket = $this->client->bucket($this->bucket); + $hash = $this->calculateHash($target->getPathname()); + + $sentObject = $bucket->upload( + fopen($target->getPathname(), 'rb'), + [ + 'name' => ($this->parent ? $this->parent . '/' : '') . $target->getFilename(), + 'metadata' => [ + 'crc32c' => $hash, + ], + ], + ); + + $result->debug(sprintf('upload: done: %s', $sentObject->name())); + $this->cleanup($target, $result); + } catch (\Exception $e) { + throw new Exception($e->getMessage(), 0, $e); + } + } + + private function calculateHash(string $filePath): string + { + return base64_encode(hex2bin(hash_file('crc32c', $filePath))); + } + + /** + * Simulate the sync execution. + * + * @param \phpbu\App\Backup\Target $target + * @param \phpbu\App\Result $result + */ + public function simulate(Target $target, Result $result) + { + $result->debug('sync backup to google cloud storage' . PHP_EOL); + + $this->isSimulation = true; + $this->simulateRemoteCleanup($target, $result); + } + + /** + * Setup google api client and google drive service. + */ + protected function createGoogleCloudClient(): StorageClient + { + if (!$this->client) { + $this->client = new StorageClient(['keyFilePath' => $this->secret]); + } + + return $this->client; + } + + /** + * Creates collector for remote cleanup. + * + * @param \phpbu\App\Backup\Target $target + * @return \phpbu\App\Backup\Collector + */ + protected function createCollector(Target $target): Collector + { + return new Collector\GoogleCloudStorage( + $target, + $this->createGoogleCloudClient()->bucket($this->bucket), + new Path($this->parent) + ); + } +} diff --git a/src/Factory.php b/src/Factory.php index 4160bdb4..3a75bbe4 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -1,4 +1,5 @@ fqcn - 'adapter' => [ + 'adapter' => [ 'array' => '\\phpbu\\App\\Adapter\\PHPArray', 'constant' => '\\phpbu\\App\\Adapter\\PHPConstant', 'dotenv' => '\\phpbu\\App\\Adapter\\Dotenv', @@ -38,10 +39,10 @@ class Factory 'wordpress' => '\\phpbu\\App\\Adapter\\WordPress', ], 'logger' => [ - 'json' => '\\phpbu\\App\\Log\\Json', - 'mail' => '\\phpbu\\App\\Log\\Mail', - 'webhook' => '\\phpbu\\App\\Log\\Webhook', - 'telegram' => '\\phpbu\\App\\Log\\Telegram', + 'json' => '\\phpbu\\App\\Log\\Json', + 'mail' => '\\phpbu\\App\\Log\\Mail', + 'webhook' => '\\phpbu\\App\\Log\\Webhook', + 'telegram' => '\\phpbu\\App\\Log\\Telegram', 'prometheus' => '\\phpbu\\App\\Log\\Prometheus', ], 'source' => [ @@ -63,31 +64,32 @@ class Factory 'sizediffpreviouspercent' => '\\phpbu\\App\\Backup\\Check\\SizeDiffPreviousPercent', 'sizediffavgpercent' => '\\phpbu\\App\\Backup\\Check\\SizeDiffAvgPercent', ], - 'crypter' => [ + 'crypter' => [ 'gpg' => '\\phpbu\\App\\Backup\\Crypter\\Gpg', 'mcrypt' => '\\phpbu\\App\\Backup\\Crypter\\Mcrypt', 'openssl' => '\\phpbu\\App\\Backup\\Crypter\\OpenSSL', ], 'sync' => [ - 'amazons3' => '\\phpbu\\App\\Backup\\Sync\\AmazonS3v3', - 'amazons3-v3' => '\\phpbu\\App\\Backup\\Sync\\AmazonS3v3', - 'amazons3-v2' => '\\phpbu\\App\\Backup\\Sync\\AmazonS3v2', - 'backblazes3' => '\\phpbu\\App\\Backup\\Sync\\BackblazeS3', - 'azureblob' => '\\phpbu\\App\\Backup\\Sync\\AzureBlob', - 'dropbox' => '\\phpbu\\App\\Backup\\Sync\\Dropbox', - 'ftp' => '\\phpbu\\App\\Backup\\Sync\\Ftp', - 'googledrive' => '\\phpbu\\App\\Backup\\Sync\\GoogleDrive', - 'rsync' => '\\phpbu\\App\\Backup\\Sync\\Rsync', - 'sftp' => '\\phpbu\\App\\Backup\\Sync\\Sftp', - 'softlayer' => '\\phpbu\\App\\Backup\\Sync\\SoftLayer', - 'openstack' => '\\phpbu\\App\\Backup\\Sync\\OpenStack', - 'yandex-disk' => '\\phpbu\\App\\Backup\\Sync\\YandexDisk', + 'amazons3' => '\\phpbu\\App\\Backup\\Sync\\AmazonS3v3', + 'amazons3-v3' => '\\phpbu\\App\\Backup\\Sync\\AmazonS3v3', + 'amazons3-v2' => '\\phpbu\\App\\Backup\\Sync\\AmazonS3v2', + 'backblazes3' => '\\phpbu\\App\\Backup\\Sync\\BackblazeS3', + 'azureblob' => '\\phpbu\\App\\Backup\\Sync\\AzureBlob', + 'dropbox' => '\\phpbu\\App\\Backup\\Sync\\Dropbox', + 'ftp' => '\\phpbu\\App\\Backup\\Sync\\Ftp', + 'googledrive' => '\\phpbu\\App\\Backup\\Sync\\GoogleDrive', + 'googlecloudstorage' => '\\phpbu\\App\\Backup\\Sync\\GoogleCloudStorage', + 'rsync' => '\\phpbu\\App\\Backup\\Sync\\Rsync', + 'sftp' => '\\phpbu\\App\\Backup\\Sync\\Sftp', + 'softlayer' => '\\phpbu\\App\\Backup\\Sync\\SoftLayer', + 'openstack' => '\\phpbu\\App\\Backup\\Sync\\OpenStack', + 'yandex-disk' => '\\phpbu\\App\\Backup\\Sync\\YandexDisk', ], 'cleaner' => [ - 'capacity' => '\\phpbu\\App\\Backup\\Cleaner\\Capacity', - 'outdated' => '\\phpbu\\App\\Backup\\Cleaner\\Outdated', - 'stepwise' => '\\phpbu\\App\\Backup\\Cleaner\\Stepwise', - 'quantity' => '\\phpbu\\App\\Backup\\Cleaner\\Quantity', + 'capacity' => '\\phpbu\\App\\Backup\\Cleaner\\Capacity', + 'outdated' => '\\phpbu\\App\\Backup\\Cleaner\\Outdated', + 'stepwise' => '\\phpbu\\App\\Backup\\Cleaner\\Stepwise', + 'quantity' => '\\phpbu\\App\\Backup\\Cleaner\\Quantity', ], ]; @@ -95,10 +97,10 @@ class Factory * Backup Factory * Creates 'Source', 'Check', 'Crypter', 'Sync' and 'Cleaner' Objects. * - * @param string $type - * @param string $alias + * @param string $type + * @param string $alias * @return mixed - *@throws Exception + * @throws Exception */ protected function create($type, $alias) { @@ -109,18 +111,19 @@ protected function create($type, $alias) throw new Exception(sprintf('unknown %s: %s', $type, $alias)); } $class = self::$classMap[$type][$alias]; + return new $class(); } /** * Adapter Factory * - * @param string $alias - * @param array $conf + * @param string $alias + * @param array $conf * @return Adapter - *@throws Exception + * @throws Exception */ - public function createAdapter($alias, $conf = []) : Adapter + public function createAdapter($alias, $conf = []): Adapter { /** @var Adapter $adapter */ $adapter = $this->create('adapter', $alias); @@ -128,18 +131,19 @@ public function createAdapter($alias, $conf = []) : Adapter throw new Exception(sprintf('adapter \'%s\' has to implement the \'Adapter\' interfaces', $alias)); } $adapter->setup($conf); + return $adapter; } /** * Logger Factory * - * @param string $alias - * @param array $conf + * @param string $alias + * @param array $conf * @return Logger - *@throws Exception + * @throws Exception */ - public function createLogger($alias, $conf = []) : Logger + public function createLogger($alias, $conf = []): Logger { /** @var Logger $logger */ $logger = $this->create('logger', $alias); @@ -150,6 +154,7 @@ public function createLogger($alias, $conf = []) : Logger throw new Exception(sprintf('logger \'%s\' has to implement the \'Listener\' interface', $alias)); } $logger->setup($conf); + return $logger; } @@ -160,7 +165,7 @@ public function createLogger($alias, $conf = []) : Logger * @return Target * @throws Exception */ - public function createTarget(Configuration\Backup\Target $conf) : Target + public function createTarget(Configuration\Backup\Target $conf): Target { $target = new Target($conf->dirname, $conf->filename); $target->setupPath(); @@ -169,18 +174,19 @@ public function createTarget(Configuration\Backup\Target $conf) : Target $compression = Target\Compression\Factory::create($conf->compression); $target->setCompression($compression); } + return $target; } /** * Source Factory * - * @param string $alias - * @param array $conf + * @param string $alias + * @param array $conf * @return Source - *@throws Exception + * @throws Exception */ - public function createSource($alias, $conf = []) : Source + public function createSource($alias, $conf = []): Source { /** @var Source $source */ $source = $this->create('source', $alias); @@ -188,35 +194,37 @@ public function createSource($alias, $conf = []) : Source throw new Exception(sprintf('source \'%s\' has to implement the \'Source\' interface', $alias)); } $source->setup($conf); + return $source; } /** * Check Factory * - * @param string $alias + * @param string $alias * @return Check - *@throws Exception + * @throws Exception */ - public function createCheck($alias) : Check + public function createCheck($alias): Check { /** @var Check $check */ $check = $this->create('check', $alias); if (!($check instanceof Check)) { throw new Exception(sprintf('Check \'%s\' has to implement the \'Check\' interface', $alias)); } + return $check; } /** * Crypter Factory * - * @param string $alias - * @param array $conf + * @param string $alias + * @param array $conf * @return Crypter - *@throws Exception + * @throws Exception */ - public function createCrypter($alias, $conf = []) : Crypter + public function createCrypter($alias, $conf = []): Crypter { /** @var Crypter $crypter */ $crypter = $this->create('crypter', $alias); @@ -224,18 +232,19 @@ public function createCrypter($alias, $conf = []) : Crypter throw new Exception(sprintf('Crypter \'%s\' has to implement the \'Crypter\' interface', $alias)); } $crypter->setup($conf); + return $crypter; } /** * Sync Factory * - * @param string $alias - * @param array $conf + * @param string $alias + * @param array $conf * @return Sync - *@throws Exception + * @throws Exception */ - public function createSync($alias, $conf = []) : Sync + public function createSync($alias, $conf = []): Sync { /** @var Sync $sync */ $sync = $this->create('sync', $alias); @@ -243,18 +252,19 @@ public function createSync($alias, $conf = []) : Sync throw new Exception(sprintf('sync \'%s\' has to implement the \'Sync\' interface', $alias)); } $sync->setup($conf); + return $sync; } /** * Cleaner Factory * - * @param string $alias - * @param array $conf + * @param string $alias + * @param array $conf * @return Cleaner - *@throws Exception + * @throws Exception */ - public function createCleaner($alias, $conf = []) : Cleaner + public function createCleaner($alias, $conf = []): Cleaner { /** @var Cleaner $cleaner */ $cleaner = $this->create('cleaner', $alias); @@ -262,16 +272,17 @@ public function createCleaner($alias, $conf = []) : Cleaner throw new Exception(sprintf('cleaner \'%s\' has to implement the \'Cleaner\' interface', $alias)); } $cleaner->setup($conf); + return $cleaner; } /** * Extend the backup factory * - * @param string $type Type to create 'adapter', 'source', 'check', 'sync' or 'cleaner' - * @param string $alias Name the class is registered at - * @param string $fqcn Full Qualified Class Name - * @param boolean $force Overwrite already registered class + * @param string $type Type to create 'adapter', 'source', 'check', 'sync' or 'cleaner' + * @param string $alias Name the class is registered at + * @param string $fqcn Full Qualified Class Name + * @param boolean $force Overwrite already registered class * @throws Exception */ public static function register($type, $alias, $fqcn, $force = false) @@ -288,14 +299,14 @@ public static function register($type, $alias, $fqcn, $force = false) /** * Throws an exception if type is invalid * - * @param string $type + * @param string $type * @throws Exception */ private static function checkType($type) { if (!isset(self::$classMap[$type])) { throw new Exception( - 'invalid type, please use only \'' . implode('\', \'', array_keys(self::$classMap)) . '\'' + 'invalid type, please use only \'' . implode('\', \'', array_keys(self::$classMap)) . '\'', ); } } diff --git a/tests/phpbu/Backup/Collector/GoogleCloudStorageTest.php b/tests/phpbu/Backup/Collector/GoogleCloudStorageTest.php new file mode 100644 index 00000000..7e8011d4 --- /dev/null +++ b/tests/phpbu/Backup/Collector/GoogleCloudStorageTest.php @@ -0,0 +1,92 @@ + + * @copyright Sebastian Feldmann + * @license https://opensource.org/licenses/MIT The MIT License (MIT) + * @link http://www.phpbu.de/ + */ +class GoogleCloudStorageTest extends TestCase +{ + /** + * Tests GoogleDrive::getBackupFiles + */ + public function testCollector() + { + $path = '/collector/static-dir/'; + $filename = 'foo-%Y-%m-%d-%H_%i.txt'; + $target = new Target($path, $filename, strtotime('2014-12-07 04:30:57')); + $bucket = $this->createMock(Bucket::class); + $path = $this->createMock(Path::class); + + $remoteFileList = [ + [ + 'name' => $target->getFilename(), + 'size' => 100, + 'last_modified' => '2018-05-08 14:14:54.0 +00:00', + ], + [ + 'name' => 'foo-2000-12-01-12_00.txt', + 'size' => 200, + 'last_modified' => '2000-12-01 12:00:00.0 +00:00', + ], + [ + 'name' => 'not-matching-2000-12-01-12_00.txt', + 'size' => 300, + 'last_modified' => '2000-12-01 12:00:00.0 +00:00', + ], + ]; + + $remoteFileList = array_map( + function ($item) { + return $this->createGoogleStorageObjectFileStub($item); + }, + $remoteFileList, + ); + + $bucket + ->expects($this->once()) + ->method('objects') + ->willReturn($remoteFileList); + + $collector = new GoogleCloudStorage($target, $bucket, $path); + $files = $collector->getBackupFiles(); + + $this->assertCount(2, $files); + $this->assertArrayHasKey('975672000-foo-2000-12-01-12_00.txt-1', $files); + $this->assertEquals( + 'foo-2000-12-01-12_00.txt', + $files['975672000-foo-2000-12-01-12_00.txt-1']->getFilename(), + ); + } + + /** + * Creates Google Storage Object file class mock. + * + * @param array $data + * @return StorageObject + */ + private function createGoogleStorageObjectFileStub(array $data): StorageObject + { + $object = $this->createMock(StorageObject::class); + $object->method('name')->willReturn($data['name']); + $object->method('info')->willReturn([ + 'size' => $data['size'], + 'updated' => $data['last_modified'], + ]); + + return $object; + } +} diff --git a/tests/phpbu/Backup/File/GoogleCloudStorageTest.php b/tests/phpbu/Backup/File/GoogleCloudStorageTest.php new file mode 100644 index 00000000..621f19c6 --- /dev/null +++ b/tests/phpbu/Backup/File/GoogleCloudStorageTest.php @@ -0,0 +1,62 @@ + + * @copyright Sebastian Feldmann + * @license https://opensource.org/licenses/MIT The MIT License (MIT) + * @link http://www.phpbu.de/ + */ +class GoogleCloudStorageTest extends TestCase +{ + /** + * Test GoogleCloudStorage::unlink + */ + public function testUnlink() + { + $file = $this->createMock(StorageObject::class); + $file->expects($this->exactly(2))->method('name')->willReturn('dump.tar.gz'); + $file->expects($this->exactly(2))->method('info')->willReturn([ + 'size' => '102102', + 'updated' => '2024-09-05T10:22:24.539Z', + ]);; + $file->expects($this->once())->method('delete'); + + $file = new GoogleCloudStorage($file); + $this->assertEquals('dump.tar.gz', $file->getFilename()); + $this->assertEquals('dump.tar.gz', $file->getPathname()); + $this->assertEquals(102102, $file->getSize()); + $this->assertEquals(1725531744, $file->getMTime()); + + $file->unlink(); + } + + /** + * Tests GoogleDrive::unlink + */ + public function testUnlinkFailure() + { + $this->expectException('phpbu\App\Exception'); + $file = $this->createMock(StorageObject::class); + $file->expects($this->exactly(2))->method('name')->willReturn('dump.tar.gz'); + $file->expects($this->exactly(2))->method('info')->willReturn([ + 'size' => '102102', + 'updated' => '2024-09-05T10:22:24.539Z', + ]);; + $file + ->expects($this->once()) + ->method('delete') + ->willThrowException(new \Exception()); + + $file = new GoogleCloudStorage($file); + $file->unlink(); + } +} diff --git a/tests/phpbu/Backup/Sync/GoogleCloudStorageTest.php b/tests/phpbu/Backup/Sync/GoogleCloudStorageTest.php new file mode 100644 index 00000000..dad58a08 --- /dev/null +++ b/tests/phpbu/Backup/Sync/GoogleCloudStorageTest.php @@ -0,0 +1,205 @@ + + * @copyright Sebastian Feldmann + * @license https://opensource.org/licenses/MIT The MIT License (MIT) + * @link http://www.phpbu.de/ + */ +class GoogleCloudStorageTest extends TestCase +{ + use BaseMockery; + + /** + * Tests GoogleCloudStorage::setUp + */ + public function testSetUpOk() + { + $sync = new GoogleCloudStorage(); + $sync->setup([ + 'secret' => PHPBU_TEST_FILES . '/misc/google_secret.json', + 'bucket' => 'fake_bucket', + 'path' => 'test/path', + ]); + + $this->assertTrue(true, 'no exception should occur'); + } + + /** + * Tests GoogleCloudStorage::simulate + */ + public function testSimulate() + { + $sync = new GoogleCloudStorage(); + $sync->setup([ + 'secret' => PHPBU_TEST_FILES . '/misc/google_secret.json', + 'bucket' => 'fake_bucket', + 'path' => 'test/path', + ]); + + $resultStub = $this->createMock(Result::class); + $resultStub->expects($this->once())->method('debug'); + + $targetStub = $this->createMock(Target::class); + + $sync->simulate($targetStub, $resultStub); + } + + /** + * Tests GoogleCloudStorage::sync + */ + public function testSync() + { + $target = $this->createTargetMock(PHPBU_TEST_FILES . '/misc/backup.txt'); + $result = $this->createMock(Result::class); + $result->expects($this->once())->method('debug'); + + $client = $this->createPartialMock(StorageClient::class, ['bucket']); + $bucket = $this->createMock(Bucket::class); + $requestObject = $this->createMock(StorageObject::class); + + $bucket + ->expects($this->once()) + ->method('upload') + ->with( + $this->isType('resource'), + [ + 'name' => 'test/path/backup.txt', + 'metadata' => [ + 'crc32c' => 'P+XTKQ==', + ], + ], + ) + ->willReturn($requestObject); + + $client + ->expects($this->once()) + ->method('bucket') + ->willReturn($bucket); + + $sync = $this->createPartialMock(GoogleCloudStorage::class, ['createGoogleCloudClient']); + $sync + ->expects($this->once()) + ->method('createGoogleCloudClient') + ->willReturn($client); + + $sync->setup([ + 'secret' => PHPBU_TEST_FILES . '/misc/google_secret.json', + 'bucket' => 'fake_bucket', + 'path' => 'test/path', + ]); + $sync->sync($target, $result); + } + + /** + * Tests GoogleCloudStorage::sync + */ + public function testSyncWithCleanup() + { + $target = $this->createTargetMock(PHPBU_TEST_FILES . '/misc/backup.txt'); + $result = $this->createMock(Result::class); + $result->expects($this->exactly(2))->method('debug'); + + $client = $this->createPartialMock(StorageClient::class, ['bucket']); + $bucket = $this->createMock(Bucket::class); + $requestObject = $this->createMock(StorageObject::class); + $collector = $this->createMock(\phpbu\App\Backup\Collector\GoogleCloudStorage::class); + + $bucket + ->expects($this->once()) + ->method('upload') + ->with( + $this->isType('resource'), + [ + 'name' => 'test/path/backup.txt', + 'metadata' => [ + 'crc32c' => 'P+XTKQ==', + ], + ], + ) + ->willReturn($requestObject); + + $client + ->expects($this->once()) + ->method('bucket') + ->willReturn($bucket); + + $sync = $this->createPartialMock(GoogleCloudStorage::class, ['createGoogleCloudClient', 'createCollector']); + $sync + ->expects($this->once()) + ->method('createGoogleCloudClient') + ->willReturn($client); + $sync + ->expects($this->exactly(1)) + ->method('createCollector') + ->willReturn($collector); + + $sync->setup([ + 'secret' => PHPBU_TEST_FILES . '/misc/google_secret.json', + 'bucket' => 'fake_bucket', + 'path' => 'test/path', + 'cleanup.type' => 'quantity', + 'cleanup.amount' => 99, + ]); + $sync->sync($target, $result); + } + + /** + * Tests GoogleCloudStorage::sync + */ + public function testSyncFail() + { + $this->expectException('phpbu\App\Exception'); + + $target = $this->createTargetMock(PHPBU_TEST_FILES . '/misc/backup.txt'); + $result = $this->createMock(Result::class); + + $sync = $this->createPartialMock(GoogleCloudStorage::class, ['createGoogleCloudClient']); + $sync + ->expects($this->once()) + ->method('createGoogleCloudClient') + ->willThrowException(new \Exception()); + + $sync->setup([ + 'secret' => PHPBU_TEST_FILES . '/misc/google_secret.json', + 'bucket' => 'fake_bucket', + 'path' => 'test/path', + ]); + $sync->sync($target, $result); + } + + /** + * Tests GoogleCloudStorage::setUp + */ + public function testSetUpNoSecret() + { + $this->expectException('phpbu\App\Backup\Sync\Exception'); + + $sync = $this->createPartialMock(GoogleCloudStorage::class, []); + $sync->setup(['secret' => '']); + } + + /** + * Tests GoogleCloudStorage::setUp + */ + public function testSetUpNoSecretFile() + { + $this->expectException('phpbu\App\Backup\Sync\Exception'); + $sync = $this->createPartialMock(GoogleCloudStorage::class, []); + $sync->setup(['secret' => 'foo']); + } +}