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']);
+ }
+}