From 17554f32edf18937798c380c79304882b72ab96f Mon Sep 17 00:00:00 2001 From: Matthew Goslett Date: Mon, 26 Sep 2016 18:12:03 +0200 Subject: [PATCH] upgrade to google cloud package + add unit tests (#36) --- .travis.yml | 4 +- README.md | 50 +- composer.json | 4 +- src/GoogleStorageAdapter.php | 297 ++++------ tests/GoogleStorageAdapterTests.php | 846 +++++++++++++++++++++++++++- 5 files changed, 992 insertions(+), 209 deletions(-) diff --git a/.travis.yml b/.travis.yml index a4ace0d..698da4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: php php: - - 5.4 - 5.5 - 5.6 + - 7.0 + - 7.1 - hhvm + - nightly before_install: - bash -c 'if [ "$TRAVIS_PHP_VERSION" == "hhvm" ]; then rm phpunit.xml; fi;' diff --git a/README.md b/README.md index 8f67085..fea92fb 100644 --- a/README.md +++ b/README.md @@ -16,28 +16,44 @@ composer require superbalist/flysystem-google-storage ## Usage ```php -use Superbalist\Flysystem\GoogleStorage\GoogleStorageAdapter; +use Google\Cloud\Storage\StorageClient; use League\Flysystem\Filesystem; +use Superbalist\Flysystem\GoogleStorage\GoogleStorageAdapter; -$credentials = new \Google_Auth_AssertionCredentials( - '[your service account]', - [\Google_Service_Storage::DEVSTORAGE_FULL_CONTROL], - file_get_contents('[[path to the p12 key file]]'), - '[[your secret]]' -); +/** + * The credentials will be auto-loaded by the Google Cloud Client. + * + * 1. The client will first look at the GOOGLE_APPLICATION_CREDENTIALS env var. + * You can use ```putenv('GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json');``` to set the location of your credentials file. + * + * 2. The client will look for the credentials file at the following paths: + * - windows: %APPDATA%/gcloud/application_default_credentials.json + * - others: $HOME/.config/gcloud/application_default_credentials.json + * + * If running in Google App Engine, the built-in service account associated with the application will be used. + * If running in Google Compute Engine, the built-in service account associated with the virtual machine instance will be used. + */ + +$storageClient = new StorageClient([ + 'projectId' => 'your-project-id', +]); +$bucket = $storageClient->bucket('your-bucket-name'); + +$adapter = new GoogleStorageAdapter($storageClient, $bucket); + +$filesystem = new Filesystem($adapter); -$client = new \Google_Client(); -$client->setAssertionCredentials($credentials); -$client->setDeveloperKey('[[your developer key]]'); +/** + * The credentials are manually specified by passing in a keyFilePath. + */ -$service = new \Google_Service_Storage($client); +$storageClient = new StorageClient([ + 'projectId' => 'your-project-id', + 'keyFilePath' => '/path/to/service-account.json', +]); +$bucket = $storageClient->bucket('your-bucket-name'); -$adapter = new GoogleStorageAdapter($service, '[[your bucket name]]'); +$adapter = new GoogleStorageAdapter($storageClient, $bucket); $filesystem = new Filesystem($adapter); ``` - - -## TODO - -* Unit tests to be written \ No newline at end of file diff --git a/composer.json b/composer.json index 86124c2..d81c67a 100644 --- a/composer.json +++ b/composer.json @@ -9,9 +9,9 @@ } ], "require": { - "php": ">=5.4.0", + "php": ">=5.5.0", "league/flysystem": "~1.0", - "google/apiclient": "~1.1" + "google/cloud": "^0.8.0" }, "require-dev": { "phpunit/phpunit": "~4.0", diff --git a/src/GoogleStorageAdapter.php b/src/GoogleStorageAdapter.php index 0dd75b7..c5176ce 100644 --- a/src/GoogleStorageAdapter.php +++ b/src/GoogleStorageAdapter.php @@ -1,53 +1,65 @@ -service = $service; + $this->storageClient = $storageClient; $this->bucket = $bucket; + + if ($pathPrefix) { + $this->setPathPrefix($pathPrefix); + } } /** - * Returns the bucket name. + * Returns the StorageClient. * - * @return string + * @return StorageClient */ - public function getBucket() + public function getStorageClient() { - return $this->bucket; + return $this->storageClient; } /** - * Returns the Google_Service_Storage service. + * Return the Bucket. * - * @return \Google_Service_Storage + * @return \Google\Cloud\Storage\Bucket */ - public function getService() + public function getBucket() { - return $this->service; + return $this->bucket; } /** @@ -61,11 +73,31 @@ public function write($path, $contents, Config $config) /** * {@inheritdoc} */ + public function writeStream($path, $resource, Config $config) + { + return $this->upload($path, $resource, $config); + } + + /** + * {@inheritdoc} + * + * @codeCoverageIgnore + */ public function update($path, $contents, Config $config) { return $this->upload($path, $contents, $config); } + /** + * {@inheritdoc} + * + * @codeCoverageIgnore + */ + public function updateStream($path, $resource, Config $config) + { + return $this->upload($path, $resource, $config); + } + /** * Returns an array of options from the config. * @@ -76,18 +108,14 @@ protected function getOptionsFromConfig(Config $config) { $options = []; - if ($config->get('visibility')) { - $options['acl'] = $config->get('visibility') === AdapterInterface::VISIBILITY_PUBLIC ? - AdapterInterface::VISIBILITY_PUBLIC : - AdapterInterface::VISIBILITY_PRIVATE; - } - - if ($config->get('mimetype')) { - $options['mimetype'] = $config->get('mimetype'); + if ($config->has('visibility')) { + $options['predefinedAcl'] = $this->getPredefinedAclForVisibility($config->get('visibility')); + } else { + // if a file is created without an acl, it isn't accessible via the console + // we therefore default to private + $options['predefinedAcl'] = $this->getPredefinedAclForVisibility(AdapterInterface::VISIBILITY_PRIVATE); } - // TODO: consider other metadata which we can set here - return $options; } @@ -95,34 +123,18 @@ protected function getOptionsFromConfig(Config $config) * Uploads a file to the Google Cloud Storage service. * * @param string $path - * @param string $contents + * @param string|resource $contents * @param Config $config * @return array */ protected function upload($path, $contents, Config $config) { - $options = $this->getOptionsFromConfig($config); + $path = $this->applyPathPrefix($path); - if (! isset($options['mimetype'])) { - $options['mimetype'] = Util::guessMimeType($path, $contents); - } - - $object = new \Google_Service_Storage_StorageObject(); - $object->setName($path); - $object->setContentType($options['mimetype']); - - $params = [ - 'data' => $contents, - 'uploadType' => 'media', - 'mimeType' => $options['mimetype'] - ]; + $options = $this->getOptionsFromConfig($config); + $options['name'] = $path; - $object = $this->service->objects->insert($this->bucket, $object, $params); - - // Only publish the file if explicitly asked. If not, default to bucket default ACL - if (isset($options['acl']) && $options['acl'] === AdapterInterface::VISIBILITY_PUBLIC) { - $this->publishObject($path); - } + $object = $this->bucket->upload($contents, $options); return $this->normaliseObject($object); } @@ -130,12 +142,14 @@ protected function upload($path, $contents, Config $config) /** * Returns a dictionary of object metadata from an object. * - * @param \Google_Service_Storage_StorageObject $object + * @param StorageObject $object * @return array */ - protected function normaliseObject(\Google_Service_Storage_StorageObject $object) + protected function normaliseObject(StorageObject $object) { - $name = $object->getName(); + $name = $object->name(); + $info = $object->info(); + $isDir = substr($name, -1) === '/'; if ($isDir) { $name = rtrim($name, '/'); @@ -145,9 +159,9 @@ protected function normaliseObject(\Google_Service_Storage_StorageObject $object 'type' => $isDir ? 'dir' : 'file', 'dirname' => Util::dirname($name), 'path' => $name, - 'timestamp' => strtotime($object->getUpdated()), - 'mimetype' => $object->getContentType(), - 'size' => $object->getSize(), + 'timestamp' => strtotime($info['updated']), + 'mimetype' => $info['contentType'], + 'size' => $info['size'], ]; } @@ -156,7 +170,7 @@ protected function normaliseObject(\Google_Service_Storage_StorageObject $object */ public function rename($path, $newpath) { - if (! $this->copy($path, $newpath)) { + if (!$this->copy($path, $newpath)) { return false; } @@ -168,21 +182,16 @@ public function rename($path, $newpath) */ public function copy($path, $newpath) { - $originalVisibility = $this->getRawVisibility($path); - - $this->service->objects->copy( - $this->bucket, - $path, - $this->bucket, - $newpath, - new \Google_Service_Storage_StorageObject() - ); - - if ($originalVisibility === AdapterInterface::VISIBILITY_PUBLIC) { - $this->publishObject($newpath); - } else { - $this->unPublishObject($newpath); - } + $newpath = $this->applyPathPrefix($newpath); + + // we want the new file to have the same visibility as the original file + $visibility = $this->getRawVisibility($path); + + $options = [ + 'name' => $newpath, + 'predefinedAcl' => $this->getPredefinedAclForVisibility($visibility), + ]; + $this->getObject($path)->copy($this->bucket, $options); return true; } @@ -192,7 +201,8 @@ public function copy($path, $newpath) */ public function delete($path) { - $this->service->objects->delete($this->bucket, $path); + $this->getObject($path)->delete(); + return true; } @@ -228,13 +238,18 @@ protected function normaliseDirName($dirname) */ public function setVisibility($path, $visibility) { + $object = $this->getObject($path); + if ($visibility === AdapterInterface::VISIBILITY_PRIVATE) { - $this->unPublishObject($path); - } else { - $this->publishObject($path); + $object->acl()->delete('allUsers'); + } else if ($visibility === AdapterInterface::VISIBILITY_PUBLIC) { + $object->acl()->add('allUsers', Acl::ROLE_READER); } - return compact('path', 'visibility'); + $normalised = $this->normaliseObject($object); + $normalised['visibility'] = $visibility; + + return $normalised; } /** @@ -242,17 +257,7 @@ public function setVisibility($path, $visibility) */ public function has($path) { - try { - // to test the existance of an object, we need to retrieve an object - // there is no api method to check if an object exists or not - $this->getObject($path); - return true; - } catch (\Google_Service_Exception $e) { - if ($e->getCode() == 404) { - return false; - } - throw $e; - } + return $this->getObject($path)->exists(); } /** @@ -260,15 +265,13 @@ public function has($path) */ public function read($path) { - // TODO: can this be optimised to not perform 2 x api calls here? $object = $this->getObject($path); - $object = $this->normaliseObject($object); - $contents = $this->service->objects->get($this->bucket, $path, ['alt' => 'media']); - if ($contents === false) { - $contents = null; - } - $object['contents'] = $contents; - return $object; + $contents = $object->downloadAsString(); + + $data = $this->normaliseObject($object); + $data['contents'] = $contents; + + return $data; } /** @@ -276,35 +279,16 @@ public function read($path) */ public function listContents($directory = '', $recursive = false) { - $results = []; - $pageToken = null; - - while (true) { - $params = []; - if ($pageToken) { - $params['pageToken'] = $pageToken; - } - - if (trim($directory) !== '') { - $params['prefix'] = $directory.'/'; - } - - $objects = $this->service->objects->listObjects($this->bucket, $params); - $results = array_merge($results, $objects->getItems()); - $pageToken = $objects->getNextPageToken(); - - if ($pageToken === null) { - break; - } + $directory = $this->applyPathPrefix($directory); + + $objects = $this->bucket->objects(['prefix' => $directory]); + + $normalised = []; + foreach ($objects as $object) { + $normalised[] = $this->normaliseObject($object); } - $results = array_map( - function (\Google_Service_Storage_StorageObject $object) { - return $this->normaliseObject($object); - }, - $results - ); - return Util::emulateDirectories($results); + return Util::emulateDirectories($normalised); } /** @@ -356,74 +340,35 @@ public function getVisibility($path) */ protected function getRawVisibility($path) { - $controls = $this->service->objectAccessControls->listObjectAccessControls($this->bucket, $path); - foreach ($controls->getItems() as $control) { - if ($this->isPublicAccessControl($control)) { - return AdapterInterface::VISIBILITY_PUBLIC; - } + try { + $acl = $this->getObject($path)->acl()->get(['entity' => 'allUsers']); + return $acl['role'] === Acl::ROLE_READER ? + AdapterInterface::VISIBILITY_PUBLIC : + AdapterInterface::VISIBILITY_PRIVATE; + } catch (NotFoundException $e) { + // object may not have an acl entry, so handle that gracefully + return AdapterInterface::VISIBILITY_PRIVATE; } - - return AdapterInterface::VISIBILITY_PRIVATE; } /** * Returns a storage object for the given path. * * @param string $path - * @return \Google_Service_Storage_StorageObject + * @return \Google\Cloud\Storage\StorageObject */ protected function getObject($path) { - return $this->service->objects->get($this->bucket, $path); + $path = $this->applyPathPrefix($path); + return $this->bucket->object($path); } /** - * Adds an ACL entry that makes the object world-readable - * - * @param string $path Object path in the current bucket - */ - protected function publishObject($path) - { - if ($this->getRawVisibility($path) === AdapterInterface::VISIBILITY_PUBLIC) { - return; - } - - $publicAcl = new \Google_Service_Storage_ObjectAccessControl(); - $publicAcl->setEntity('allUsers'); - $publicAcl->setRole('READER'); - $this->service->objectAccessControls->insert($this->bucket, $path, $publicAcl); - } - - /** - * Removes a `READER` role for entity `allUsers` from the object if present. - * - * @param string $path - */ - protected function unPublishObject($path) - { - $controls = $this->service->objectAccessControls->listObjectAccessControls($this->bucket, $path); - // Cycle through existent entries in the ACL and only delete the `allUsers` entry if it is set to `allUsers` - foreach ($controls->getItems() as $control) { - if ($this->isPublicAccessControl($control)) { - $this->service->objectAccessControls->delete($this->bucket, $path, 'allUsers'); - break; - } - } - } - - /** - * Checks whether the given Access Control List entry marks the object - * as published. - * - * By default that is the case when the special entity 'allUsers' has - * the role 'READER'. - * - * @param \Google_Service_Storage_ObjectAccessControl $control - * - * @return bool + * @param string $visibility + * @return string */ - protected function isPublicAccessControl($control) + protected function getPredefinedAclForVisibility($visibility) { - return $control['role'] === 'READER' && $control['entity'] === 'allUsers'; + return $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'publicRead' : 'projectPrivate'; } } diff --git a/tests/GoogleStorageAdapterTests.php b/tests/GoogleStorageAdapterTests.php index 69f27ec..5c46293 100644 --- a/tests/GoogleStorageAdapterTests.php +++ b/tests/GoogleStorageAdapterTests.php @@ -1,23 +1,843 @@ assertSame($storageClient, $adapter->getStorageClient()); + } + + public function testGetBucket() { - return true; + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + $adapter = new GoogleStorageAdapter($storageClient, $bucket); + + $this->assertSame($bucket, $adapter->getBucket()); } -} -class GoogleStorageTests extends PHPUnit_Framework_TestCase -{ + public function testWrite() + { + $bucket = Mockery::mock(Bucket::class); - // TODO: implement all unit tests + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file1.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); - public function testTrueIsTrue() - { - $this->assertTrue(true); - } -} \ No newline at end of file + $bucket->shouldReceive('upload') + ->withArgs([ + 'This is the file contents.', + [ + 'name' => 'prefix/file1.txt', + 'predefinedAcl' => 'projectPrivate', + ] + ]) + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $data = $adapter->write('file1.txt', 'This is the file contents.', new Config()); + + $expected = [ + 'type' => 'file', + 'dirname' => 'prefix', + 'path' => 'prefix/file1.txt', + 'timestamp' => 1474901082, + 'mimetype' => 'text/plain', + 'size' => 5, + ]; + $this->assertEquals($expected, $data); + } + + public function testWriteWithPrivateVisibility() + { + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file1.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('upload') + ->withArgs([ + 'This is the file contents.', + [ + 'name' => 'prefix/file1.txt', + 'predefinedAcl' => 'projectPrivate', + ] + ]) + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $data = $adapter->write('file1.txt', 'This is the file contents.', new Config(['visibility' => AdapterInterface::VISIBILITY_PRIVATE])); + + $expected = [ + 'type' => 'file', + 'dirname' => 'prefix', + 'path' => 'prefix/file1.txt', + 'timestamp' => 1474901082, + 'mimetype' => 'text/plain', + 'size' => 5, + ]; + $this->assertEquals($expected, $data); + } + + public function testWriteWithPublicVisibility() + { + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file1.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('upload') + ->withArgs([ + 'This is the file contents.', + [ + 'name' => 'prefix/file1.txt', + 'predefinedAcl' => 'publicRead', + ] + ]) + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $data = $adapter->write('file1.txt', 'This is the file contents.', new Config(['visibility' => AdapterInterface::VISIBILITY_PUBLIC])); + + $expected = [ + 'type' => 'file', + 'dirname' => 'prefix', + 'path' => 'prefix/file1.txt', + 'timestamp' => 1474901082, + 'mimetype' => 'text/plain', + 'size' => 5, + ]; + $this->assertEquals($expected, $data); + } + + public function testWriteStream() + { + $stream = tmpfile(); + + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file1.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('upload') + ->withArgs([ + $stream, + [ + 'name' => 'prefix/file1.txt', + 'predefinedAcl' => 'projectPrivate', + ] + ]) + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $data = $adapter->writeStream('file1.txt', $stream, new Config()); + + fclose($stream); + + $expected = [ + 'type' => 'file', + 'dirname' => 'prefix', + 'path' => 'prefix/file1.txt', + 'timestamp' => 1474901082, + 'mimetype' => 'text/plain', + 'size' => 5, + ]; + $this->assertEquals($expected, $data); + } + + public function testRename() + { + $bucket = Mockery::mock(Bucket::class); + + $oldStorageObjectAcl = Mockery::mock(Acl::class); + $oldStorageObjectAcl->shouldReceive('get') + ->with(['entity' => 'allUsers']) + ->once() + ->andReturn([ + 'role' => Acl::ROLE_OWNER, + ]); + + $oldStorageObject = Mockery::mock(StorageObject::class); + $oldStorageObject->shouldReceive('acl') + ->once() + ->andReturn($oldStorageObjectAcl); + $oldStorageObject->shouldReceive('copy') + ->withArgs([ + $bucket, + [ + 'name' => 'prefix/new_file.txt', + 'predefinedAcl' => 'projectPrivate', + ], + ]) + ->once(); + $oldStorageObject->shouldReceive('delete') + ->once(); + + $bucket->shouldReceive('object') + ->with('prefix/old_file.txt') + ->times(3) + ->andReturn($oldStorageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $adapter->rename('old_file.txt', 'new_file.txt'); + } + + public function testCopy() + { + $bucket = Mockery::mock(Bucket::class); + + $oldStorageObjectAcl = Mockery::mock(Acl::class); + $oldStorageObjectAcl->shouldReceive('get') + ->with(['entity' => 'allUsers']) + ->once() + ->andReturn([ + 'role' => Acl::ROLE_OWNER, + ]); + + $oldStorageObject = Mockery::mock(StorageObject::class); + $oldStorageObject->shouldReceive('acl') + ->once() + ->andReturn($oldStorageObjectAcl); + $oldStorageObject->shouldReceive('copy') + ->withArgs([ + $bucket, + [ + 'name' => 'prefix/new_file.txt', + 'predefinedAcl' => 'projectPrivate', + ], + ]) + ->once(); + + $bucket->shouldReceive('object') + ->with('prefix/old_file.txt') + ->times(2) + ->andReturn($oldStorageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $adapter->copy('old_file.txt', 'new_file.txt'); + } + + public function testCopyWhenOriginalFileIsPublic() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $oldStorageObjectAcl = Mockery::mock(Acl::class); + $oldStorageObjectAcl->shouldReceive('get') + ->with(['entity' => 'allUsers']) + ->once() + ->andReturn([ + 'role' => Acl::ROLE_READER, + ]); + + $oldStorageObject = Mockery::mock(StorageObject::class); + $oldStorageObject->shouldReceive('acl') + ->once() + ->andReturn($oldStorageObjectAcl); + $oldStorageObject->shouldReceive('copy') + ->withArgs([ + $bucket, + [ + 'name' => 'prefix/new_file.txt', + 'predefinedAcl' => 'publicRead', + ], + ]) + ->once(); + + $bucket->shouldReceive('object') + ->with('prefix/old_file.txt') + ->times(2) + ->andReturn($oldStorageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $adapter->copy('old_file.txt', 'new_file.txt'); + } + + public function testDelete() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('delete') + ->once(); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $adapter->delete('file.txt'); + } + + public function testDeleteDir() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('delete') + ->once(); + + $bucket->shouldReceive('object') + ->with('prefix/dir_name/') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $adapter->deleteDir('dir_name'); + } + + public function testDeleteDirWithTrailingSlash() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('delete') + ->once(); + + $bucket->shouldReceive('object') + ->with('prefix/dir_name/') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $adapter->deleteDir('dir_name//'); + } + + public function testSetVisibilityPrivate() + { + $bucket = Mockery::mock(Bucket::class); + + $storageObjectAcl = Mockery::mock(Acl::class); + $storageObjectAcl->shouldReceive('delete') + ->with('allUsers') + ->once(); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('acl') + ->once() + ->andReturn($storageObjectAcl); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/file1.txt') + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $data = $adapter->setVisibility('file1.txt', AdapterInterface::VISIBILITY_PRIVATE); + $this->assertArrayHasKey('visibility', $data); + $this->assertEquals(AdapterInterface::VISIBILITY_PRIVATE, $data['visibility']); + } + + public function testSetVisibilityPublic() + { + $bucket = Mockery::mock(Bucket::class); + + $storageObjectAcl = Mockery::mock(Acl::class); + $storageObjectAcl->shouldReceive('add') + ->withArgs([ + 'allUsers', + Acl::ROLE_READER, + ]) + ->once(); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('acl') + ->once() + ->andReturn($storageObjectAcl); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/file1.txt') + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $data = $adapter->setVisibility('file1.txt', AdapterInterface::VISIBILITY_PUBLIC); + $this->assertArrayHasKey('visibility', $data); + $this->assertEquals(AdapterInterface::VISIBILITY_PUBLIC, $data['visibility']); + } + + public function testHas() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('exists') + ->once(); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $adapter->has('file.txt'); + } + + public function testRead() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('downloadAsString') + ->once() + ->andReturn('This is the file contents.'); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $data = $adapter->read('file.txt'); + + $this->assertArrayHasKey('contents', $data); + $this->assertEquals('This is the file contents.', $data['contents']); + } + + public function testListContents() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $bucket->shouldReceive('objects') + ->once() + ->with([ + 'prefix' => 'prefix/', + ]) + ->andReturn($this->getMockDirObjects()); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $listing = $adapter->listContents(); + + $expected = [ + [ + 'type' => 'dir', + 'dirname' => '', + 'path' => 'directory1', + 'timestamp' => 1474901082, + 'mimetype' => 'application/octet-stream', + 'size' => 0, + ], + [ + 'type' => 'file', + 'dirname' => 'directory1', + 'path' => 'directory1/file1.txt', + 'timestamp' => 1474901082, + 'mimetype' => 'text/plain', + 'size' => 5, + ], + [ + 'type' => 'file', + 'dirname' => 'directory2', + 'path' => 'directory2/file1.txt', + 'timestamp' => 1474901082, + 'mimetype' => 'text/plain', + 'size' => 5, + ], + [ + 'dirname' => '', + 'basename' => 'directory2', + 'filename' => 'directory2', + 'path' => 'directory2', + 'type' => 'dir', + ], + ]; + + $this->assertEquals($expected, $listing); + } + + /** + * @return array + */ + protected function getMockDirObjects() + { + $dir1 = Mockery::mock(StorageObject::class); + $dir1->shouldReceive('name') + ->once() + ->andReturn('directory1/'); + $dir1->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'application/octet-stream', + 'size' => 0, + ]); + + $dir1file1 = Mockery::mock(StorageObject::class); + $dir1file1->shouldReceive('name') + ->once() + ->andReturn('directory1/file1.txt'); + $dir1file1->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $dir2file1 = Mockery::mock(StorageObject::class); + $dir2file1->shouldReceive('name') + ->once() + ->andReturn('directory2/file1.txt'); + $dir2file1->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + return [ + $dir1, + $dir1file1, + $dir2file1, + ]; + } + + public function testGetMetadataForFile() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $metadata = $adapter->getMetadata('file.txt'); + + $expected = [ + 'type' => 'file', + 'dirname' => 'prefix', + 'path' => 'prefix/file.txt', + 'timestamp' => 1474901082, + 'mimetype' => 'text/plain', + 'size' => 5, + ]; + + $this->assertEquals($expected, $metadata); + } + + public function testGetMetadataForDir() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/directory/'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'application/octet-stream', + 'size' => 0, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/directory') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $metadata = $adapter->getMetadata('directory'); + + $expected = [ + 'type' => 'dir', + 'dirname' => 'prefix', + 'path' => 'prefix/directory', + 'timestamp' => 1474901082, + 'mimetype' => 'application/octet-stream', + 'size' => 0, + ]; + + $this->assertEquals($expected, $metadata); + } + + public function testGetSize() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $metadata = $adapter->getMetadata('file.txt'); + + $this->assertArrayHasKey('size', $metadata); + $this->assertEquals(5, $metadata['size']); + } + + public function testGetMimetype() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $metadata = $adapter->getMetadata('file.txt'); + + $this->assertArrayHasKey('mimetype', $metadata); + $this->assertEquals('text/plain', $metadata['mimetype']); + } + + public function testGetTimestamp() + { + $storageClient = Mockery::mock(StorageClient::class); + $bucket = Mockery::mock(Bucket::class); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('name') + ->once() + ->andReturn('prefix/file.txt'); + $storageObject->shouldReceive('info') + ->once() + ->andReturn([ + 'updated' => '2016-09-26T14:44:42+00:00', + 'contentType' => 'text/plain', + 'size' => 5, + ]); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $metadata = $adapter->getMetadata('file.txt'); + + $this->assertArrayHasKey('timestamp', $metadata); + $this->assertEquals(1474901082, $metadata['timestamp']); + } + + public function testGetVisibilityWhenVisibilityIsPrivate() + { + $bucket = Mockery::mock(Bucket::class); + + $storageObjectAcl = Mockery::mock(Acl::class); + $storageObjectAcl->shouldReceive('get') + ->with(['entity' => 'allUsers']) + ->once() + ->andReturn([ + 'role' => Acl::ROLE_OWNER, + ]); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('acl') + ->once() + ->andReturn($storageObjectAcl); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $visibility = $adapter->getVisibility('file.txt'); + $this->assertEquals(['visibility' => AdapterInterface::VISIBILITY_PRIVATE], $visibility); + } + + public function testGetVisibilityWhenVisibilityIsPublic() + { + $bucket = Mockery::mock(Bucket::class); + + $storageObjectAcl = Mockery::mock(Acl::class); + $storageObjectAcl->shouldReceive('get') + ->with(['entity' => 'allUsers']) + ->once() + ->andReturn([ + 'role' => Acl::ROLE_READER, + ]); + + $storageObject = Mockery::mock(StorageObject::class); + $storageObject->shouldReceive('acl') + ->once() + ->andReturn($storageObjectAcl); + + $bucket->shouldReceive('object') + ->with('prefix/file.txt') + ->once() + ->andReturn($storageObject); + + $storageClient = Mockery::mock(StorageClient::class); + + $adapter = new GoogleStorageAdapter($storageClient, $bucket, 'prefix'); + + $visibility = $adapter->getVisibility('file.txt'); + $this->assertEquals(['visibility' => AdapterInterface::VISIBILITY_PUBLIC], $visibility); + } +}