diff --git a/.travis.yml b/.travis.yml index 9748397..6ac7558 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,10 @@ services: language: php php: - - 5.6 - - 7.0 - - 7.1 - 7.2 - 7.3 - 7.4 + - 8.0 install: - travis_retry composer install --no-interaction --prefer-source diff --git a/composer.json b/composer.json index 64d4c38..9fac7fb 100644 --- a/composer.json +++ b/composer.json @@ -9,12 +9,12 @@ } ], "require": { - "php": ">=5.6", - "league/flysystem": "~1.0", + "php": ">=7.2", + "league/flysystem": "~2.0", "sabre/dav": "~4.0|~3.1" }, "require-dev": { - "phpunit/phpunit": "~4.8", + "phpunit/phpunit": "~8.5", "mockery/mockery": "~1.2" }, "autoload": { diff --git a/src/WebDAVAdapter.php b/src/WebDAVAdapter.php index c6a8eed..6b7667f 100644 --- a/src/WebDAVAdapter.php +++ b/src/WebDAVAdapter.php @@ -2,27 +2,29 @@ namespace League\Flysystem\WebDAV; -use League\Flysystem\Adapter\AbstractAdapter; -use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait; -use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait; -use League\Flysystem\Adapter\Polyfill\StreamedReadingTrait; use League\Flysystem\Config; -use League\Flysystem\Util; +use League\Flysystem\DirectoryAttributes; +use League\Flysystem\FileAttributes; +use League\Flysystem\FilesystemAdapter; +use League\Flysystem\FilesystemException; +use League\Flysystem\StorageAttributes; +use League\Flysystem\UnableToCopyFile; +use League\Flysystem\UnableToCreateDirectory; +use League\Flysystem\UnableToDeleteDirectory; +use League\Flysystem\UnableToDeleteFile; +use League\Flysystem\UnableToMoveFile; +use League\Flysystem\UnableToReadFile; +use League\Flysystem\UnableToRetrieveMetadata; +use League\Flysystem\UnableToWriteFile; use LogicException; +use RuntimeException; use Sabre\DAV\Client; -use Sabre\DAV\Exception; -use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Xml\Property\ResourceType; -use Sabre\HTTP\HttpException; +use Sabre\HTTP\ClientException; +use Sabre\HTTP\ClientHttpException; -class WebDAVAdapter extends AbstractAdapter +class WebDAVAdapter implements FilesystemAdapter { - use StreamedReadingTrait; - use StreamedCopyTrait { - StreamedCopyTrait::copy as streamedCopy; - } - use NotSupportingVisibilityTrait; - protected static $metadataFields = [ '{DAV:}displayname', '{DAV:}getcontentlength', @@ -32,38 +34,20 @@ class WebDAVAdapter extends AbstractAdapter '{DAV:}resourcetype', ]; - /** - * @var array - */ - protected static $resultMap = [ - '{DAV:}getcontentlength' => 'size', - '{DAV:}getcontenttype' => 'mimetype', - 'content-length' => 'size', - 'content-type' => 'mimetype', - ]; - /** * @var Client */ protected $client; - /** - * @var bool - */ - protected $useStreamedCopy = true; - /** * Constructor. * * @param Client $client - * @param string $prefix - * @param bool $useStreamedCopy */ - public function __construct(Client $client, $prefix = null, $useStreamedCopy = true) + public function __construct(Client $client) { + $client->setThrowExceptions(true); $this->client = $client; - $this->setPathPrefix($prefix); - $this->setUseStreamedCopy($useStreamedCopy); } /** @@ -73,333 +57,334 @@ public function __construct(Client $client, $prefix = null, $useStreamedCopy = t * * @return string */ - protected function encodePath($path) + protected function encodePath(string $path): string { - $a = explode('/', $path); - for ($i=0; $iapplyPathPrefix($this->encodePath($path)); + $class = __CLASS__; + throw new LogicException("$class does not support visibility. Path: $path"); + } + + public function setVisibility(string $path, string $visibility): void + { + $class = __CLASS__; + throw new LogicException("$class does not support visibility. Path: $path, visibility: $visibility"); + } + + private function getMetadata(string $path, string $metadataType): ?StorageAttributes + { + $location = $this->encodePath($path); try { $result = $this->client->propFind($location, static::$metadataFields); + } catch (ClientException | ClientHttpException $exception) { + throw UnableToRetrieveMetadata::create($path, $metadataType, '', $exception); + } - if (empty($result)) { - return false; - } + if (empty($result)) { + return null; + } - return $this->normalizeObject($result, $path); - } catch (Exception $e) { - return false; - } catch (HttpException $e) { - return false; + $path = trim($path, '/'); + if ($this->isDirectory($result)) { + return DirectoryAttributes::fromArray([StorageAttributes::ATTRIBUTE_PATH => $path]); } + $lastModified = $object['{DAV:}getlastmodified'] ?? null; + return FileAttributes::fromArray([ + StorageAttributes::ATTRIBUTE_PATH => $path, + StorageAttributes::ATTRIBUTE_FILE_SIZE => $object['content-length'] ?? $object['{DAV:}getcontentlength'] ?? null, + StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $lastModified !== null ? strtotime($lastModified) : null, + StorageAttributes::ATTRIBUTE_MIME_TYPE => $object['content-type'] ?? $object['{DAV:}getcontenttype'] ?? null, + ]); } - /** - * {@inheritdoc} - */ - public function has($path) + public function fileExists(string $path): bool { - return $this->getMetadata($path); + try { + return $this->getMetadata($path, 'fileExists') !== null; + } catch (FilesystemException $exception) { + return false; + } } /** * {@inheritdoc} */ - public function read($path) + public function read(string $path): string { - $location = $this->applyPathPrefix($this->encodePath($path)); - try { - $response = $this->client->request('GET', $location); - - if ($response['statusCode'] !== 200) { - return false; - } + [ + 'body' => $body, + 'statusCode' => $statusCode, + 'headers' => $headers, + ] = $this->client->request('GET', $this->encodePath($path)); + } catch (ClientException | ClientHttpException $exception) { + throw UnableToReadFile::fromLocation($path, '', $exception); + } - return array_merge([ - 'contents' => $response['body'], - 'timestamp' => strtotime(is_array($response['headers']['last-modified']) - ? current($response['headers']['last-modified']) - : $response['headers']['last-modified']), - 'path' => $path, - ], Util::map($response['headers'], static::$resultMap)); - } catch (Exception $e) { - return false; + if ($statusCode !== 200) { + throw UnableToReadFile::fromLocation($path, "HTTP status code is $statusCode, not 200."); } + + return $body; } /** * {@inheritdoc} */ - public function write($path, $contents, Config $config) + public function readStream(string $path) { - if (!$this->createDir(Util::dirname($path), $config)) { - return false; - } - - $location = $this->applyPathPrefix($this->encodePath($path)); - $response = $this->client->request('PUT', $location, $contents); + $data = $this->read($path); - if ($response['statusCode'] >= 400) { - return false; + $stream = fopen('php://temp', 'w+b'); + if ($stream === false) { + throw new RuntimeException("opening temporary stream failed"); } - - $result = compact('path', 'contents'); - - if ($config->get('visibility')) { - throw new LogicException(__CLASS__.' does not support visibility settings.'); - } - - return $result; + fwrite($stream, $data); + rewind($stream); + return $stream; } /** - * {@inheritdoc} + * @param string $path + * @param string|resource $contents + * @param Config $config + * @throws FilesystemException */ - public function writeStream($path, $resource, Config $config) + protected function writeImpl(string $path, $contents, Config $config): void { - return $this->write($path, $resource, $config); + if ($config->get(StorageAttributes::ATTRIBUTE_VISIBILITY)) { + throw new LogicException(__CLASS__.' does not support visibility settings.'); + } + + $directory = dirname($path); + if ($directory === '.') { + $directory = ''; + } + $this->createDirectory($directory, $config); + + $location = $this->encodePath($path); + try { + $this->client->request('PUT', $location, $contents); + } catch (ClientException | ClientHttpException $exception) { + throw UnableToWriteFile::atLocation($path, '', $exception); + } } /** * {@inheritdoc} */ - public function update($path, $contents, Config $config) + public function write(string $path, string $contents, Config $config): void { - return $this->write($path, $contents, $config); + $this->writeImpl($path, $contents, $config); } /** * {@inheritdoc} */ - public function updateStream($path, $resource, Config $config) + public function writeStream(string $path, $contents, Config $config): void { - return $this->update($path, $resource, $config); + $this->writeImpl($path, $contents, $config); } /** * {@inheritdoc} */ - public function rename($path, $newpath) + public function move(string $source, string $destination, Config $config): void { - $location = $this->applyPathPrefix($this->encodePath($path)); - $newLocation = $this->applyPathPrefix($this->encodePath($newpath)); + $location = $this->encodePath($source); + $newLocation = $this->encodePath($destination); try { - $response = $this->client->request('MOVE', '/'.ltrim($location, '/'), null, [ - 'Destination' => '/'.ltrim($newLocation, '/'), + ['statusCode' => $statusCode] = $this->client->request('MOVE', '/' . ltrim($location, '/'), null, [ + 'Destination' => '/' . ltrim($newLocation, '/'), ]); - - if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) { - return true; - } - } catch (NotFound $e) { - // Would have returned false here, but would be redundant + } catch (ClientException | ClientHttpException $exception) { + throw UnableToMoveFile::fromLocationTo($source, $destination, $exception); + } + if ($statusCode < 200 || 300 <= $statusCode) { + throw UnableToMoveFile::fromLocationTo($source, $destination); } - - return false; } /** * {@inheritdoc} */ - public function copy($path, $newpath) + public function copy(string $source, string $destination, Config $config): void { - if ($this->useStreamedCopy === true) { - return $this->streamedCopy($path, $newpath); - } else { - return $this->nativeCopy($path, $newpath); - } + $this->nativeCopy($source, $destination, $config); } /** - * {@inheritdoc} + * @param string $path + * @param string|UnableToDeleteFile|UnableToDeleteDirectory $exceptionToThrow */ - public function delete($path) + public function deleteImpl(string $path, string $exceptionToThrow): void { - $location = $this->applyPathPrefix($this->encodePath($path)); + $location = $this->encodePath($path); try { - $response = $this->client->request('DELETE', $location)['statusCode']; - + ['statusCode' => $statusCode] = $this->client->request('DELETE', $location); + } catch (ClientException | ClientHttpException $exception) { + throw $exceptionToThrow::atLocation($path, '', $exception); + } - return $response >= 200 && $response < 300; - } catch (NotFound $e) { - return false; + if ($statusCode < 200 || 300 <= $statusCode) { + throw $exceptionToThrow::atLocation($path); } } + public function delete(string $path): void + { + $this->deleteImpl($path, UnableToDeleteFile::class); + } + /** * {@inheritdoc} */ - public function createDir($path, Config $config) + public function createDirectory(string $path, Config $config): void { $encodedPath = $this->encodePath($path); $path = trim($path, '/'); - $result = compact('path') + ['type' => 'dir']; - - if (Util::normalizeDirname($path) === '' || $this->has($path)) { - return $result; + if ($path === '.' || $path === '' || $this->fileExists($path)) { + return; } $directories = explode('/', $path); if (count($directories) > 1) { $parentDirectories = array_splice($directories, 0, count($directories) - 1); - if (!$this->createDir(implode('/', $parentDirectories), $config)) { - return false; - } + $this->createDirectory(implode('/', $parentDirectories), $config); } - $location = $this->applyPathPrefix($encodedPath); - $response = $this->client->request('MKCOL', $location . $this->pathSeparator); + ['statusCode' => $statusCode] = $this->client->request('MKCOL', $encodedPath . '/'); - if ($response['statusCode'] !== 201) { - return false; + if ($statusCode !== 201) { + throw UnableToCreateDirectory::atLocation($path); } - - return $result; } /** * {@inheritdoc} */ - public function deleteDir($dirname) + public function deleteDirectory(string $path): void { - return $this->delete($dirname); + $this->deleteImpl($path, UnableToDeleteDirectory::class); } /** * {@inheritdoc} */ - public function listContents($directory = '', $recursive = false) + public function listContents(string $path, bool $deep): iterable { - $location = $this->applyPathPrefix($this->encodePath($directory)); - $response = $this->client->propFind($location . '/', static::$metadataFields, 1); + try { + $response = $this->client->propFind($this->encodePath($path) . '/', static::$metadataFields, 1); + } catch (ClientException | ClientHttpException $exception) { + throw UnableToRetrieveMetadata::create($path, 'listContents', '', $exception); + } array_shift($response); - $result = []; - - foreach ($response as $path => $object) { - $path = $this->removePathPrefix(rawurldecode($path)); - $object = $this->normalizeObject($object, $path); - $result[] = $object; - if ($recursive && $object['type'] === 'dir') { - $result = array_merge($result, $this->listContents($object['path'], true)); + foreach ($response as $rawChildPath => $object) { + $childPath = trim(rawurldecode($rawChildPath), '/'); + if ($this->isDirectory($object)) { + yield DirectoryAttributes::fromArray([ + StorageAttributes::ATTRIBUTE_PATH => $childPath + ]); + if ($deep) { + yield from $this->listContents($childPath, true); + } + } else { + $lastModified = $object['{DAV:}getlastmodified'] ?? null; + $fileSize = $object['content-length'] ?? $object['{DAV:}getcontentlength'] ?? null; + if ($fileSize !== null) { + $fileSize = (int)$fileSize; + } + yield FileAttributes::fromArray([ + StorageAttributes::ATTRIBUTE_PATH => $childPath, + StorageAttributes::ATTRIBUTE_FILE_SIZE => $fileSize, + StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $lastModified !== null ? strtotime($lastModified) : null, + StorageAttributes::ATTRIBUTE_MIME_TYPE => $object['content-type'] ?? $object['{DAV:}getcontenttype'] ?? null, + ]); } } - - return $result; } - /** - * {@inheritdoc} - */ - public function getSize($path) + private static function ensureFileAttributes(?StorageAttributes $metadata, string $path, string $metadataType): FileAttributes { - return $this->getMetadata($path); + if ($metadata === null) { + throw UnableToRetrieveMetadata::create($path, $metadataType, 'file not found'); + } + if ($metadata instanceof DirectoryAttributes) { + throw UnableToRetrieveMetadata::create($path, $metadataType, 'not a file'); + } + if ($metadata instanceof FileAttributes) { + return $metadata; + } + throw new LogicException("never happen"); } /** * {@inheritdoc} */ - public function getTimestamp($path) + public function fileSize(string $path): FileAttributes { - return $this->getMetadata($path); + $metadata = $this->getMetadata($path, StorageAttributes::ATTRIBUTE_FILE_SIZE); + return self::ensureFileAttributes($metadata, $path, StorageAttributes::ATTRIBUTE_FILE_SIZE); } /** * {@inheritdoc} */ - public function getMimetype($path) + public function lastModified(string $path): FileAttributes { - return $this->getMetadata($path); + $metadata = $this->getMetadata($path, StorageAttributes::ATTRIBUTE_LAST_MODIFIED); + return self::ensureFileAttributes($metadata, $path, StorageAttributes::ATTRIBUTE_LAST_MODIFIED); } /** - * @return boolean - */ - public function getUseStreamedCopy() - { - return $this->useStreamedCopy; - } - - /** - * @param boolean $useStreamedCopy + * {@inheritdoc} */ - public function setUseStreamedCopy($useStreamedCopy) + public function mimeType(string $path): FileAttributes { - $this->useStreamedCopy = (bool)$useStreamedCopy; + $metadata = $this->getMetadata($path, StorageAttributes::ATTRIBUTE_MIME_TYPE); + return self::ensureFileAttributes($metadata, $path, StorageAttributes::ATTRIBUTE_MIME_TYPE); } /** * Copy a file through WebDav COPY method. * - * @param string $path - * @param string $newPath - * - * @return bool + * @param string $source + * @param string $destination + * @throws FilesystemException */ - protected function nativeCopy($path, $newPath) + protected function nativeCopy(string $source, string $destination, Config $config): void { - if (!$this->createDir(Util::dirname($newPath), new Config())) { - return false; + $directory = dirname($destination); + if ($directory === '.') { + $directory = ''; } + $this->createDirectory($directory, $config); - $location = $this->applyPathPrefix($this->encodePath($path)); - $newLocation = $this->applyPathPrefix($this->encodePath($newPath)); - + $location = $this->encodePath($source); try { - $destination = $this->client->getAbsoluteUrl($newLocation); - $response = $this->client->request('COPY', '/'.ltrim($location, '/'), null, [ - 'Destination' => $destination, + ['statusCode' => $statusCode] = $this->client->request('COPY', '/' . ltrim($location, '/'), null, [ + 'Destination' => $this->client->getAbsoluteUrl($this->encodePath($destination)), ]); - - if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) { - return true; - } - } catch (NotFound $e) { - // Would have returned false here, but would be redundant + } catch (ClientException | ClientHttpException $exception) { + throw UnableToCopyFile::fromLocationTo($source, $destination, $exception); } - return false; - } - - /** - * Normalise a WebDAV repsonse object. - * - * @param array $object - * @param string $path - * - * @return array - */ - protected function normalizeObject(array $object, $path) - { - if ($this->isDirectory($object)) { - return ['type' => 'dir', 'path' => trim($path, '/')]; - } - - $result = Util::map($object, static::$resultMap); - - if (isset($object['{DAV:}getlastmodified'])) { - $result['timestamp'] = strtotime($object['{DAV:}getlastmodified']); + if ($statusCode < 200 || 300 <= $statusCode) { + throw UnableToCopyFile::fromLocationTo($source, $destination); } - - $result['type'] = 'file'; - $result['path'] = trim($path, '/'); - - return $result; } /** @@ -408,12 +393,11 @@ protected function normalizeObject(array $object, $path) */ protected function isDirectory(array $object) { - if (isset($object['{DAV:}resourcetype'])) { - /** @var ResourceType $resourceType */ - $resourceType = $object['{DAV:}resourcetype']; + $resourceType = $object['{DAV:}resourcetype'] ?? null; + if ($resourceType instanceof ResourceType) { return $resourceType->is('{DAV:}collection'); } - return isset($object['{DAV:}iscollection']) && $object['{DAV:}iscollection'] === '1'; + return ($object['{DAV:}iscollection'] ?? null) === '1'; } } diff --git a/tests/WebDAVTests.php b/tests/WebDAVTests.php index 147e03c..e9f52f8 100644 --- a/tests/WebDAVTests.php +++ b/tests/WebDAVTests.php @@ -2,45 +2,51 @@ use League\Flysystem\Config; use League\Flysystem\Filesystem; +use League\Flysystem\UnableToCreateDirectory; +use League\Flysystem\UnableToDeleteDirectory; +use League\Flysystem\UnableToMoveFile; +use League\Flysystem\UnableToReadFile; +use League\Flysystem\UnableToWriteFile; use League\Flysystem\WebDAV\WebDAVAdapter; use PHPUnit\Framework\TestCase; +use Sabre\DAV\Client; +use Sabre\HTTP\Response; class WebDAVTests extends TestCase { use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; + /** + * @return Mockery\LegacyMockInterface|Mockery\MockInterface|Client + */ protected function getClient() { - return Mockery::mock('Sabre\DAV\Client'); + $mock = Mockery::mock(Sabre\DAV\Client::class); + $mock->shouldReceive('setThrowExceptions')->once(); + return $mock; + } + + protected function newClientHttpException(int $httpStatus, array $headers = [], ?string $body = null) + { + return new Sabre\HTTP\ClientHttpException(new Response($httpStatus, $headers, $body)); } - public function testHas() + public function testFileExists() { $mock = $this->getClient(); $mock->shouldReceive('propFind')->once()->andReturn([ '{DAV:}getcontentlength' => 20, ]); $adapter = new Filesystem(new WebDAVAdapter($mock)); - $this->assertTrue($adapter->has('something')); + $this->assertTrue($adapter->fileExists('something')); } - /** - * @dataProvider provideExceptionsForHasFail - */ - public function testHasFail($exceptionClass) + public function testHasFail() { $mock = $this->getClient(); - $mock->shouldReceive('propFind')->once()->andThrow($exceptionClass); + $mock->shouldReceive('propFind')->once()->andThrow($this->newClientHttpException(404)); $adapter = new WebDAVAdapter($mock); - $this->assertFalse($adapter->has('something')); - } - - public function provideExceptionsForHasFail() - { - return [ - [Mockery::mock('Sabre\DAV\Exception\NotFound')], - [Mockery::mock('Sabre\HTTP\ClientHttpException')], - ]; + $this->assertFalse($adapter->fileExists('something')); } public function testWrite() @@ -50,18 +56,16 @@ public function testWrite() 'statusCode' => 200, ]); $adapter = new WebDAVAdapter($mock); - $this->assertInternalType('array', $adapter->write('something', 'something', new Config())); + $adapter->write('something', 'something', new Config()); } public function testWriteFail() { $mock = $this->getClient(); - $mock->shouldReceive('request')->with('PUT', 'something', 'something')->once()->andReturn([ - 'statusCode' => 500, - ]); + $mock->shouldReceive('request')->with('PUT', 'something', 'something')->once()->andThrow($this->newClientHttpException(500)); $adapter = new WebDAVAdapter($mock); - $result = $adapter->write('something', 'something', new Config()); - $this->assertFalse($result); + $this->expectException(UnableToWriteFile::class); + $adapter->write('something', 'something', new Config()); } public function testWriteStream() @@ -74,7 +78,7 @@ public function testWriteStream() $tmp = $this->getLargeTmpStream(); - $this->assertInternalType('array', $adapter->writeStream('something', $tmp, new Config())); + $adapter->writeStream('something', $tmp, new Config()); if (is_resource($tmp)) { fclose($tmp); @@ -83,7 +87,7 @@ public function testWriteStream() protected function getLargeTmpStream() { - $size = intval($this->getMemoryLimit() * 1.5); + $size = (int)($this->getMemoryLimit() * 1.5); $tmp = tmpfile(); fseek($tmp, $size); fprintf($tmp, 'a'); @@ -102,37 +106,21 @@ protected function getMemoryLimit() ]; if (!preg_match("/^(\d+)([KMG]?)$/i", ini_get('memory_limit'), $match)) { - throw new Exception('invalid memory_limit?'); + throw new UnexpectedValueException('invalid memory_limit?'); } - $limit = $match[1] * pow(1024, $unit_factor[strtoupper($match[2])]); - - return $limit; - } - - public function testUpdate() - { - $mock = $this->getClient(); - $mock->shouldReceive('request') - ->once() - ->andReturn(['statusCode' => 201]); - $adapter = new WebDAVAdapter($mock); - $this->assertInternalType('array', $adapter->update('something', 'something', new Config())); + return $match[1] * (1024 ** $unit_factor[strtoupper($match[2])]); } - /** - * @expectedException LogicException - */ public function testWriteVisibility() { $mock = $this->getClient(); - $mock->shouldReceive('request')->once()->andReturn([ - 'statusCode' => 200, - ]); + $mock->shouldReceive('request')->never(); $adapter = new WebDAVAdapter($mock); - $this->assertInternalType('array', $adapter->write('something', 'something', new Config([ + $this->expectException(LogicException::class); + $adapter->write('something', 'something', new Config([ 'visibility' => 'private', - ]))); + ])); } public function testReadStream() @@ -145,9 +133,16 @@ public function testReadStream() 'last-modified' => date('Y-m-d H:i:s'), ], ]); - $adapter = new WebDAVAdapter($mock, 'bucketname', 'prefix'); - $result = $adapter->readStream('file.txt'); - $this->assertInternalType('resource', $result['stream']); + $adapter = new WebDAVAdapter($mock); + $resource = $adapter->readStream('file.txt'); + $this->assertIsResource($resource); + $result = ""; + while (!feof($resource)) { + $read = fread($resource, 100); + $this->assertIsString($read); + $result .= $read; + } + $this->assertSame('contents', $result); } public function testRename() @@ -156,9 +151,8 @@ public function testRename() $mock->shouldReceive('request')->once()->andReturn([ 'statusCode' => 200, ]); - $adapter = new WebDAVAdapter($mock, 'bucketname'); - $result = $adapter->rename('old', 'new'); - $this->assertTrue($result); + $adapter = new WebDAVAdapter($mock); + $adapter->move('old', 'new', new Config()); } public function testRenameFail() @@ -167,18 +161,18 @@ public function testRenameFail() $mock->shouldReceive('request')->once()->andReturn([ 'statusCode' => 404, ]); - $adapter = new WebDAVAdapter($mock, 'bucketname'); - $result = $adapter->rename('old', 'new'); - $this->assertFalse($result); + $adapter = new WebDAVAdapter($mock); + $this->expectException(UnableToMoveFile::class); + $adapter->move('old', 'new', new Config()); } public function testRenameFailException() { $mock = $this->getClient(); - $mock->shouldReceive('request')->once()->andThrow('Sabre\DAV\Exception\NotFound'); - $adapter = new WebDAVAdapter($mock, 'bucketname'); - $result = $adapter->rename('old', 'new'); - $this->assertFalse($result); + $mock->shouldReceive('request')->once()->andThrow($this->newClientHttpException(500)); + $adapter = new WebDAVAdapter($mock); + $this->expectException(UnableToMoveFile::class); + $adapter->move('old', 'new', new Config()); } public function testDeleteDir() @@ -186,17 +180,16 @@ public function testDeleteDir() $mock = $this->getClient(); $mock->shouldReceive('request')->with('DELETE', 'some/dirname')->once()->andReturn(['statusCode' => 200]); $adapter = new WebDAVAdapter($mock); - $result = $adapter->deleteDir('some/dirname'); - $this->assertTrue($result); + $adapter->deleteDirectory('some/dirname'); } public function testDeleteDirFailNotFound() { $mock = $this->getClient(); - $mock->shouldReceive('request')->with('DELETE', 'some/dirname')->once()->andThrow('Sabre\DAV\Exception\NotFound'); + $mock->shouldReceive('request')->with('DELETE', 'some/dirname')->once()->andThrow($this->newClientHttpException(404)); $adapter = new WebDAVAdapter($mock); - $result = $adapter->deleteDir('some/dirname'); - $this->assertFalse($result); + $this->expectException(UnableToDeleteDirectory::class); + $adapter->deleteDirectory('some/dirname'); } public function testDeleteDirFailNot200Status() @@ -204,8 +197,8 @@ public function testDeleteDirFailNot200Status() $mock = $this->getClient(); $mock->shouldReceive('request')->with('DELETE', 'some/dirname')->once()->andReturn(['statusCode' => 403]); $adapter = new WebDAVAdapter($mock); - $result = $adapter->deleteDir('some/dirname'); - $this->assertFalse($result); + $this->expectException(UnableToDeleteDirectory::class); + $adapter->deleteDirectory('some/dirname'); } public function testListContents() @@ -231,9 +224,10 @@ public function testListContents() ], ]; $mock->shouldReceive('propFind')->twice()->andReturn($first, $second); - $adapter = new WebDAVAdapter($mock, 'bucketname'); + $adapter = new WebDAVAdapter($mock); $listing = $adapter->listContents('', true); - $this->assertInternalType('array', $listing); + $this->assertInstanceOf(Generator::class, $listing); + iterator_to_array($listing); } public function testListContentsWithPlusInName() @@ -248,11 +242,12 @@ public function testListContentsWithPlusInName() ]; $mock->shouldReceive('propFind')->once()->andReturn($first); - $adapter = new WebDAVAdapter($mock, 'bucketname'); + $adapter = new WebDAVAdapter($mock); $listing = $adapter->listContents('', false); - $this->assertInternalType('array', $listing); + $this->assertInstanceOf(Generator::class, $listing); + $listing = iterator_to_array($listing); $this->assertCount(1, $listing); - $this->assertEquals('dirname+something', $listing[0]['path']); + $this->assertEquals('bucketname/dirname+something', $listing[0]['path']); } public function testListContentsWithUrlEncodedSpaceInName() @@ -267,29 +262,31 @@ public function testListContentsWithUrlEncodedSpaceInName() ]; $mock->shouldReceive('propFind')->once()->andReturn($first); - $adapter = new WebDAVAdapter($mock, '/My Library'); + $adapter = new WebDAVAdapter($mock); $listing = $adapter->listContents('', false); - $this->assertInternalType('array', $listing); + $this->assertInstanceOf(Generator::class, $listing); + $listing = iterator_to_array($listing); $this->assertCount(1, $listing); - $this->assertEquals('New Record 1.mp3', $listing[0]['path']); - $this->assertEquals('file', $listing[0]['type']); - $this->assertEquals('8223370', $listing[0]['size']); + $attributes = $listing[0]; + $this->assertInstanceOf(\League\Flysystem\FileAttributes::class, $attributes); + $this->assertEquals('My Library/New Record 1.mp3', $attributes->path()); + $this->assertEquals('file', $attributes->type()); + $this->assertEquals(8223370, $attributes->fileSize()); } - public function methodProvider() + public function methodProvider(): array { return [ - ['getMetadata'], - ['getTimestamp'], - ['getMimetype'], - ['getSize'], + ['lastModified'], + ['mimeType'], + ['fileSize'], ]; } /** * @dataProvider methodProvider */ - public function testMetaMethods($method) + public function testMetaMethods(string $method) { $mock = $this->getClient(); $mock->shouldReceive('propFind')->once()->andReturn([ @@ -300,17 +297,16 @@ public function testMetaMethods($method) ]); $adapter = new WebDAVAdapter($mock); $result = $adapter->{$method}('object.ext'); - $this->assertInternalType('array', $result); + $this->assertInstanceOf(\League\Flysystem\FileAttributes::class, $result); } public function testCreateDir() { - /** @var Sabre\DAV\Client|Mockery\Mock $mock */ $mock = $this->getClient(); $mock->shouldReceive('propFind') ->once() - ->andThrow(new \Sabre\DAV\Exception('Not found')); + ->andThrow($this->newClientHttpException(404)); $mock->shouldReceive('request') ->once() @@ -320,18 +316,16 @@ public function testCreateDir() ]); $adapter = new WebDAVAdapter($mock); - $result = $adapter->createDir('dirname', new Config()); - $this->assertInternalType('array', $result); + $adapter->createDirectory('dirname', new Config()); } public function testCreateDirRecursive() { - /** @var Sabre\DAV\Client|Mockery\Mock $mock */ $mock = $this->getClient(); $mock->shouldReceive('propFind') ->times(2) - ->andThrow(new \Sabre\DAV\Exception('Not found')); + ->andThrow($this->newClientHttpException(404)); $mock->shouldReceive('request') ->once() @@ -348,13 +342,11 @@ public function testCreateDirRecursive() ]); $adapter = new WebDAVAdapter($mock); - $result = $adapter->createDir('dirname/subdirname', new Config()); - $this->assertInternalType('array', $result); + $adapter->createDirectory('dirname/subdirname', new Config()); } public function testCreateDirIfExists() { - /** @var Sabre\DAV\Client|Mockery\Mock $mock */ $mock = $this->getClient(); $mock->shouldReceive('propFind') @@ -370,18 +362,16 @@ public function testCreateDirIfExists() ->never(); $adapter = new WebDAVAdapter($mock); - $result = $adapter->createDir('dirname', new Config()); - $this->assertInternalType('array', $result); + $adapter->createDirectory('dirname', new Config()); } public function testCreateDirFail() { - /** @var Sabre\DAV\Client|Mockery\Mock $mock */ $mock = $this->getClient(); $mock->shouldReceive('propFind') ->once() - ->andThrow(new \Sabre\DAV\Exception('Not found')); + ->andThrow($this->newClientHttpException(404)); $mock->shouldReceive('request') ->once() @@ -391,8 +381,8 @@ public function testCreateDirFail() ]); $adapter = new WebDAVAdapter($mock); - $result = $adapter->createDir('dirname', new Config()); - $this->assertFalse($result); + $this->expectException(UnableToCreateDirectory::class); + $adapter->createDirectory('dirname', new Config()); } public function testRead() @@ -405,9 +395,9 @@ public function testRead() 'last-modified' => [date('Y-m-d H:i:s')], ], ]); - $adapter = new WebDAVAdapter($mock, 'bucketname', 'prefix'); + $adapter = new WebDAVAdapter($mock); $result = $adapter->read('file.txt'); - $this->assertInternalType('array', $result); + $this->assertSame('contents', $result); } public function testReadFail() @@ -420,9 +410,9 @@ public function testReadFail() 'last-modified' => [date('Y-m-d H:i:s')], ], ]); - $adapter = new WebDAVAdapter($mock, 'bucketname', 'prefix'); - $result = $adapter->read('file.txt'); - $this->assertFalse($result); + $adapter = new WebDAVAdapter($mock); + $this->expectException(UnableToReadFile::class); + $adapter->read('file.txt'); } public function testReadStreamFail() @@ -435,23 +425,22 @@ public function testReadStreamFail() 'last-modified' => [date('Y-m-d H:i:s')], ], ]); - $adapter = new WebDAVAdapter($mock, 'bucketname', 'prefix'); - $result = $adapter->readStream('file.txt'); - $this->assertFalse($result); + $adapter = new WebDAVAdapter($mock); + $this->expectException(UnableToReadFile::class); + $adapter->readStream('file.txt'); } public function testReadException() { $mock = $this->getClient(); - $mock->shouldReceive('request')->andThrow('Sabre\DAV\Exception\NotFound'); - $adapter = new WebDAVAdapter($mock, 'bucketname', 'prefix'); - $result = $adapter->read('file.txt'); - $this->assertFalse($result); + $mock->shouldReceive('request')->andThrow($this->newClientHttpException(404)); + $adapter = new WebDAVAdapter($mock); + $this->expectException(UnableToReadFile::class); + $adapter->read('file.txt'); } public function testNativeCopy() { - /** @var Sabre\DAV\Client|Mockery\Mock $clientMock */ $clientMock = $this->getClient(); $clientMock->shouldReceive('getAbsoluteUrl')->andReturn('http://webdav.local/prefix/newFile.txt'); @@ -460,8 +449,7 @@ public function testNativeCopy() 'statusCode' => 201 ]); - $adapter = new WebDAVAdapter($clientMock, 'prefix', false); - $result = $adapter->copy('file.txt', 'newFile.txt'); - $this->assertTrue($result); + $adapter = new WebDAVAdapter($clientMock); + $adapter->copy('file.txt', 'newFile.txt', new Config()); } }