[BUGFIX] Make meta data editable for non-writable storages
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Resource / ResourceStorageTest.php
index 2527371..c1ebb56 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+declare(strict_types = 1);
+
 namespace TYPO3\CMS\Core\Tests\Unit\Resource;
 
 /*
@@ -14,12 +16,20 @@ namespace TYPO3\CMS\Core\Tests\Unit\Resource;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Prophecy\Argument;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Resource\Driver\AbstractDriver;
 use TYPO3\CMS\Core\Resource\Driver\LocalDriver;
+use TYPO3\CMS\Core\Resource\DuplicationBehavior;
+use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
 use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\FileInterface;
 use TYPO3\CMS\Core\Resource\FileRepository;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
+use TYPO3\CMS\Core\Resource\Index\Indexer;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -30,31 +40,33 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class ResourceStorageTest extends BaseTestCase
 {
     /**
-     * @var array A backup of registered singleton instances
+     * @var bool Reset singletons created by subject
      */
-    protected $singletonInstances = [];
+    protected $resetSingletonInstances = true;
 
     /**
      * @var ResourceStorage|\PHPUnit_Framework_MockObject_MockObject
      */
     protected $subject;
 
-    protected function setUp()
+    /**
+     * Set up
+     */
+    protected function setUp(): void
     {
         parent::setUp();
-        $this->singletonInstances = GeneralUtility::getSingletonInstances();
         /** @var FileRepository|\PHPUnit_Framework_MockObject_MockObject $fileRepositoryMock */
         $fileRepositoryMock = $this->createMock(FileRepository::class);
         GeneralUtility::setSingletonInstance(
             FileRepository::class,
             $fileRepositoryMock
         );
-    }
-
-    protected function tearDown()
-    {
-        GeneralUtility::resetSingletonInstances($this->singletonInstances);
-        parent::tearDown();
+        $cacheManagerProphecy = $this->prophesize(CacheManager::class);
+        $cacheProphecy = $this->prophesize(FrontendInterface::class);
+        $cacheManagerProphecy->getCache('cache_runtime')->willReturn($cacheProphecy->reveal());
+        $cacheProphecy->get(Argument::cetera())->willReturn(false);
+        $cacheProphecy->set(Argument::cetera())->willReturn(false);
+        GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
     }
 
     /**
@@ -64,27 +76,40 @@ class ResourceStorageTest extends BaseTestCase
      * @param bool $mockPermissionChecks
      * @param AbstractDriver|\PHPUnit_Framework_MockObject_MockObject $driverObject
      * @param array $storageRecord
+     * @param array $mockedMethods
      */
-    protected function prepareSubject(array $configuration, $mockPermissionChecks = false, AbstractDriver $driverObject = null, array $storageRecord = [])
-    {
-        $permissionMethods = ['assureFileAddPermissions', 'checkFolderActionPermission', 'checkFileActionPermission', 'checkUserActionPermission', 'checkFileExtensionPermission', 'isWithinFileMountBoundaries'];
-        $mockedMethods = [];
+    protected function prepareSubject(
+        array $configuration,
+        bool $mockPermissionChecks = false,
+        AbstractDriver $driverObject = null,
+        array $storageRecord = [],
+        array $mockedMethods = []
+    ): void {
+        $permissionMethods = [
+            'assureFileAddPermissions',
+            'checkFolderActionPermission',
+            'checkFileActionPermission',
+            'checkUserActionPermission',
+            'checkFileExtensionPermission',
+            'isWithinFileMountBoundaries',
+            'assureFileRenamePermissions'
+        ];
         $configuration = $this->convertConfigurationArrayToFlexformXml($configuration);
         $overruleArray = ['configuration' => $configuration];
         ArrayUtility::mergeRecursiveWithOverrule($storageRecord, $overruleArray);
-        if ($driverObject == null) {
+        if ($driverObject === null) {
             $driverObject = $this->getMockForAbstractClass(AbstractDriver::class, [], '', false);
         }
         if ($mockPermissionChecks) {
-            $mockedMethods = $permissionMethods;
+            $mockedMethods = array_merge($mockedMethods, $permissionMethods);
         }
         $mockedMethods[] = 'getIndexer';
 
         $this->subject = $this->getMockBuilder(ResourceStorage::class)
-            ->setMethods($mockedMethods)
+            ->setMethods(array_unique($mockedMethods))
             ->setConstructorArgs([$driverObject, $storageRecord])
             ->getMock();
-        $this->subject->expects($this->any())->method('getIndexer')->will($this->returnValue($this->createMock(\TYPO3\CMS\Core\Resource\Index\Indexer::class)));
+        $this->subject->expects($this->any())->method('getIndexer')->will($this->returnValue($this->createMock(Indexer::class)));
         if ($mockPermissionChecks) {
             foreach ($permissionMethods as $method) {
                 $this->subject->expects($this->any())->method($method)->will($this->returnValue(true));
@@ -99,7 +124,7 @@ class ResourceStorageTest extends BaseTestCase
      * @return string
      * @see GeneralUtility::array2xml()
      */
-    protected function convertConfigurationArrayToFlexformXml(array $configuration)
+    protected function convertConfigurationArrayToFlexformXml(array $configuration): string
     {
         $flexFormArray = ['data' => ['sDEF' => ['lDEF' => []]]];
         foreach ($configuration as $key => $value) {
@@ -119,8 +144,11 @@ class ResourceStorageTest extends BaseTestCase
      * @param array $mockedDriverMethods
      * @return \TYPO3\CMS\Core\Resource\Driver\LocalDriver|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected function createDriverMock($driverConfiguration, ResourceStorage $storageObject = null, $mockedDriverMethods = [])
-    {
+    protected function createDriverMock(
+        $driverConfiguration,
+        ResourceStorage $storageObject = null,
+        array $mockedDriverMethods = []
+    ) {
         $this->initializeVfs();
 
         if (!isset($driverConfiguration['basePath'])) {
@@ -131,7 +159,7 @@ class ResourceStorageTest extends BaseTestCase
             $driver = new LocalDriver($driverConfiguration);
         } else {
             // We are using the LocalDriver here because PHPUnit can't mock concrete methods in abstract classes, so
-                // when using the AbstractDriver we would be in trouble when wanting to mock away some concrete method
+            // when using the AbstractDriver we would be in trouble when wanting to mock away some concrete method
             $driver = $this->getMockBuilder(LocalDriver::class)
                 ->setMethods($mockedDriverMethods)
                 ->setConstructorArgs([$driverConfiguration])
@@ -149,58 +177,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @return array
      */
-    public function fileExtensionPermissionDataProvider()
-    {
-        return [
-            'Permissions evaluated, extension not in allowed list' => [
-                'fileName' => 'foo.txt',
-                'configuration' => ['allow' => 'jpg'],
-                'evaluatePermissions' => true,
-                'isAllowed' => true,
-            ],
-            'Permissions evaluated, extension in deny list' => [
-                'fileName' => 'foo.txt',
-                'configuration' => ['deny' => 'txt'],
-                'evaluatePermissions' => true,
-                'isAllowed' => false,
-            ],
-            'Permissions not evaluated, extension is php' => [
-                'fileName' => 'foo.php',
-                'configuration' => [],
-                'evaluatePermissions' => false,
-                'isAllowed' => false,
-            ],
-            'Permissions evaluated, extension is php' => [
-                'fileName' => 'foo.php',
-                // It is not possible to allow php file extension through configuration
-                'configuration' => ['allow' => 'php'],
-                'evaluatePermissions' => true,
-                'isAllowed' => false,
-            ],
-        ];
-    }
-
-    /**
-     * @param string $fileName
-     * @param array $configuration
-     * @param bool $evaluatePermissions
-     * @param bool $isAllowed
-     * @test
-     * @dataProvider fileExtensionPermissionDataProvider
-     */
-    public function fileExtensionPermissionIsWorkingCorrectly($fileName, array $configuration, $evaluatePermissions, $isAllowed)
-    {
-        $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']['webspace'] = $configuration;
-        $driverMock = $this->getMockForAbstractClass(AbstractDriver::class, [], '', false);
-        $subject = $this->getAccessibleMock(ResourceStorage::class, ['dummy'], [$driverMock, []]);
-        $subject->_set('evaluatePermissions', $evaluatePermissions);
-        $this->assertSame($isAllowed, $subject->_call('checkFileExtensionPermission', $fileName));
-    }
-
-    /**
-     * @return array
-     */
-    public function capabilitiesDataProvider()
+    public function capabilitiesDataProvider(): array
     {
         return [
             'only public' => [
@@ -245,8 +222,9 @@ class ResourceStorageTest extends BaseTestCase
      * @test
      * @dataProvider capabilitiesDataProvider
      * @TODO: Rewrite or move to functional suite
+     * @param array $capabilities
      */
-    public function capabilitiesOfStorageObjectAreCorrectlySet(array $capabilities)
+    public function capabilitiesOfStorageObjectAreCorrectlySet(array $capabilities): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $storageRecord = [
@@ -264,26 +242,41 @@ class ResourceStorageTest extends BaseTestCase
             null
         );
         $this->prepareSubject([], false, $mockedDriver, $storageRecord);
-        $this->assertEquals($capabilities['public'], $this->subject->isPublic(), 'Capability "public" is not correctly set.');
-        $this->assertEquals($capabilities['writable'], $this->subject->isWritable(), 'Capability "writable" is not correctly set.');
-        $this->assertEquals($capabilities['browsable'], $this->subject->isBrowsable(), 'Capability "browsable" is not correctly set.');
+        $this->assertEquals(
+            $capabilities['public'],
+            $this->subject->isPublic(),
+            'Capability "public" is not correctly set.'
+        );
+        $this->assertEquals(
+            $capabilities['writable'],
+            $this->subject->isWritable(),
+            'Capability "writable" is not correctly set.'
+        );
+        $this->assertEquals(
+            $capabilities['browsable'],
+            $this->subject->isBrowsable(),
+            'Capability "browsable" is not correctly set.'
+        );
     }
 
     /**
      * @test
      * @TODO: Rewrite or move to functional suite
      */
-    public function fileAndFolderListFiltersAreInitializedWithDefaultFilters()
+    public function fileAndFolderListFiltersAreInitializedWithDefaultFilters(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $this->prepareSubject([]);
-        $this->assertEquals($GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'], $this->subject->getFileAndFolderNameFilters());
+        $this->assertEquals(
+            $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks'],
+            $this->subject->getFileAndFolderNameFilters()
+        );
     }
 
     /**
      * @test
      */
-    public function addFileFailsIfFileDoesNotExist()
+    public function addFileFailsIfFileDoesNotExist(): void
     {
         /** @var Folder|\PHPUnit_Framework_MockObject_MockObject $mockedFolder */
         $mockedFolder = $this->createMock(Folder::class);
@@ -296,7 +289,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function getPublicUrlReturnsNullIfStorageIsNotOnline()
+    public function getPublicUrlReturnsNullIfStorageIsNotOnline(): void
     {
         /** @var $driver LocalDriver|\PHPUnit_Framework_MockObject_MockObject */
         $driver = $this->getMockBuilder(LocalDriver::class)
@@ -320,7 +313,7 @@ class ResourceStorageTest extends BaseTestCase
      *
      * @return array
      */
-    public function checkFolderPermissionsFilesystemPermissionsDataProvider()
+    public function checkFolderPermissionsFilesystemPermissionsDataProvider(): array
     {
         return [
             'read action on readable/writable folder' => [
@@ -348,14 +341,17 @@ class ResourceStorageTest extends BaseTestCase
      * @param array $permissionsFromDriver The permissions as returned from the driver
      * @param bool $expectedResult
      */
-    public function checkFolderPermissionsRespectsFilesystemPermissions($action, $permissionsFromDriver, $expectedResult)
-    {
+    public function checkFolderPermissionsRespectsFilesystemPermissions(
+        string $action,
+        array $permissionsFromDriver,
+        bool $expectedResult
+    ): void {
         /** @var $mockedDriver LocalDriver|\PHPUnit_Framework_MockObject_MockObject */
         $mockedDriver = $this->createMock(LocalDriver::class);
         $mockedDriver->expects($this->any())->method('getPermissions')->will($this->returnValue($permissionsFromDriver));
-        /** @var $mockedFolder Folder|\PHPUnit_Framework_MockObject_MockObject  */
+        /** @var $mockedFolder Folder|\PHPUnit_Framework_MockObject_MockObject */
         $mockedFolder = $this->createMock(Folder::class);
-            // Let all other checks pass
+        // Let all other checks pass
         /** @var $subject ResourceStorage|\PHPUnit_Framework_MockObject_MockObject */
         $subject = $this->getMockBuilder(ResourceStorage::class)
             ->setMethods(['isWritable', 'isBrowsable', 'checkUserActionPermission'])
@@ -372,7 +368,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function checkUserActionPermissionsAlwaysReturnsTrueIfNoUserPermissionsAreSet()
+    public function checkUserActionPermissionsAlwaysReturnsTrueIfNoUserPermissionsAreSet(): void
     {
         $this->prepareSubject([]);
         $this->assertTrue($this->subject->checkUserActionPermission('read', 'folder'));
@@ -381,14 +377,17 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function checkUserActionPermissionReturnsFalseIfPermissionIsSetToZero()
+    public function checkUserActionPermissionReturnsFalseIfPermissionIsSetToZero(): void
     {
         $this->prepareSubject([]);
         $this->subject->setUserPermissions(['readFolder' => true, 'writeFile' => true]);
         $this->assertTrue($this->subject->checkUserActionPermission('read', 'folder'));
     }
 
-    public function checkUserActionPermission_arbitraryPermissionDataProvider()
+    /**
+     * @return array
+     */
+    public function checkUserActionPermission_arbitraryPermissionDataProvider(): array
     {
         return [
             'all lower cased' => [
@@ -416,7 +415,7 @@ class ResourceStorageTest extends BaseTestCase
      * @test
      * @dataProvider checkUserActionPermission_arbitraryPermissionDataProvider
      */
-    public function checkUserActionPermissionAcceptsArbitrarilyCasedArguments(array $permissions, $action, $type)
+    public function checkUserActionPermissionAcceptsArbitrarilyCasedArguments(array $permissions, string $action, string $type): void
     {
         $this->prepareSubject([]);
         $this->subject->setUserPermissions($permissions);
@@ -426,7 +425,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function userActionIsDisallowedIfPermissionIsSetToFalse()
+    public function userActionIsDisallowedIfPermissionIsSetToFalse(): void
     {
         $this->prepareSubject([]);
         $this->subject->setEvaluatePermissions(true);
@@ -437,7 +436,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function userActionIsDisallowedIfPermissionIsNotSet()
+    public function userActionIsDisallowedIfPermissionIsNotSet(): void
     {
         $this->prepareSubject([]);
         $this->subject->setEvaluatePermissions(true);
@@ -448,7 +447,72 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function getEvaluatePermissionsWhenSetFalse()
+    public function metaDataEditIsNotAllowedWhenWhenNoFileMountsAreSet(): void
+    {
+        $this->prepareSubject([], false, null, [], ['isWithinProcessingFolder']);
+        $this->subject->setEvaluatePermissions(true);
+        $this->assertFalse($this->subject->checkFileActionPermission('editMeta', new File(['identifier' => '/foo/bar.jpg'], $this->subject)));
+    }
+
+    /**
+     * @test
+     */
+    public function metaDataEditIsAllowedWhenWhenInFileMount(): void
+    {
+        $driverMock = $this->getMockForAbstractClass(AbstractDriver::class, [], '', false);
+        $this->prepareSubject([], false, $driverMock, [], ['isWithinProcessingFolder']);
+
+        $fileStub = new File(['identifier' => '/foo/bar.jpg'], $this->subject);
+        $folderStub = new Folder($this->subject, '/foo/', 'foo');
+        $driverMock->expects($this->once())
+            ->method('isWithin')
+            ->with($folderStub->getIdentifier(), $fileStub->getIdentifier())
+            ->willReturn(true);
+
+        $this->subject->setEvaluatePermissions(true);
+        $fileMounts = [
+            '/foo/' => [
+                'path' => '/foo/',
+                'title' => 'Foo',
+                'folder' => $folderStub,
+            ]
+        ];
+        $this->inject($this->subject, 'fileMounts', $fileMounts);
+        $this->assertTrue($this->subject->checkFileActionPermission('editMeta', $fileStub));
+    }
+
+    /**
+     * @test
+     */
+    public function metaDataEditIsNotAllowedWhenWhenInReadOnlyFileMount(): void
+    {
+        $driverMock = $this->getMockForAbstractClass(AbstractDriver::class, [], '', false);
+        $this->prepareSubject([], false, $driverMock, [], ['isWithinProcessingFolder']);
+
+        $fileStub = new File(['identifier' => '/foo/bar.jpg'], $this->subject);
+        $folderStub = new Folder($this->subject, '/foo/', 'foo');
+        $driverMock->expects($this->once())
+            ->method('isWithin')
+            ->with($folderStub->getIdentifier(), $fileStub->getIdentifier())
+            ->willReturn(true);
+
+        $this->subject->setEvaluatePermissions(true);
+        $fileMounts = [
+            '/foo/' => [
+                'path' => '/foo/',
+                'title' => 'Foo',
+                'folder' => $folderStub,
+                'read_only' => true,
+            ]
+        ];
+        $this->inject($this->subject, 'fileMounts', $fileMounts);
+        $this->assertFalse($this->subject->checkFileActionPermission('editMeta', $fileStub));
+    }
+
+    /**
+     * @test
+     */
+    public function getEvaluatePermissionsWhenSetFalse(): void
     {
         $this->prepareSubject([]);
         $this->subject->setEvaluatePermissions(false);
@@ -458,7 +522,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function getEvaluatePermissionsWhenSetTrue()
+    public function getEvaluatePermissionsWhenSetTrue(): void
     {
         $this->prepareSubject([]);
         $this->subject->setEvaluatePermissions(true);
@@ -470,7 +534,7 @@ class ResourceStorageTest extends BaseTestCase
      * @group integration
      * @TODO: Rewrite or move to functional suite
      */
-    public function setFileContentsUpdatesObjectProperties()
+    public function setFileContentsUpdatesObjectProperties(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $this->initializeVfs();
@@ -526,7 +590,7 @@ class ResourceStorageTest extends BaseTestCase
      * @group integration
      * @TODO: Rewrite or move to functional suite
      */
-    public function moveFileCallsDriversMethodsWithCorrectArguments()
+    public function moveFileCallsDriversMethodsWithCorrectArguments(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $localFilePath = '/path/to/localFile';
@@ -560,7 +624,11 @@ class ResourceStorageTest extends BaseTestCase
             ->setConstructorArgs([['basePath' => $this->getMountRootUrl()]])
             ->getMock();
         $mockedDriver->expects($this->once())->method('getFileInfoByIdentifier')->will($this->returnValue($fileInfoDummy));
-        $mockedDriver->expects($this->once())->method('addFile')->with($localFilePath, '/targetFolder/', $this->equalTo('file.ext'))->will($this->returnValue('/targetFolder/file.ext'));
+        $mockedDriver->expects($this->once())->method('addFile')->with(
+            $localFilePath,
+            '/targetFolder/',
+            $this->equalTo('file.ext')
+        )->will($this->returnValue('/targetFolder/file.ext'));
         /** @var $subject ResourceStorage */
         $subject = $this->getMockBuilder(ResourceStorage::class)
             ->setMethods(['assureFileMovePermissions'])
@@ -574,7 +642,7 @@ class ResourceStorageTest extends BaseTestCase
      * @group integration
      * @TODO: Rewrite or move to functional suite
      */
-    public function storageUsesInjectedFilemountsToCheckForMountBoundaries()
+    public function storageUsesInjectedFilemountsToCheckForMountBoundaries(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $mockedFile = $this->getSimpleFileMock('/mountFolder/file');
@@ -595,7 +663,7 @@ class ResourceStorageTest extends BaseTestCase
      * @test
      * @TODO: Rewrite or move to functional suite
      */
-    public function createFolderChecksIfParentFolderExistsBeforeCreatingFolder()
+    public function createFolderChecksIfParentFolderExistsBeforeCreatingFolder(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $mockedParentFolder = $this->getSimpleFolderMock('/someFolder/');
@@ -610,7 +678,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function deleteFolderThrowsExceptionIfFolderIsNotEmptyAndRecursiveDeleteIsDisabled()
+    public function deleteFolderThrowsExceptionIfFolderIsNotEmptyAndRecursiveDeleteIsDisabled(): void
     {
         $this->expectException(\RuntimeException::class);
         $this->expectExceptionCode(1325952534);
@@ -620,7 +688,7 @@ class ResourceStorageTest extends BaseTestCase
         /** @var $mockedDriver \TYPO3\CMS\Core\Resource\Driver\AbstractDriver|\PHPUnit_Framework_MockObject_MockObject */
         $mockedDriver = $this->getMockForAbstractClass(AbstractDriver::class);
         $mockedDriver->expects($this->once())->method('isFolderEmpty')->will($this->returnValue(false));
-        /** @var $subject ResourceStorage|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
+        /** @var $subject ResourceStorage|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
         $subject = $this->getAccessibleMock(ResourceStorage::class, ['checkFolderActionPermission'], [], '', false);
         $subject->expects($this->any())->method('checkFolderActionPermission')->will($this->returnValue(true));
         $subject->_set('driver', $mockedDriver);
@@ -631,13 +699,16 @@ class ResourceStorageTest extends BaseTestCase
      * @test
      * @TODO: Rewrite or move to functional suite
      */
-    public function createFolderCallsDriverForFolderCreation()
+    public function createFolderCallsDriverForFolderCreation(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $mockedParentFolder = $this->getSimpleFolderMock('/someFolder/');
         $this->prepareSubject([], true);
         $mockedDriver = $this->createDriverMock([], $this->subject);
-        $mockedDriver->expects($this->once())->method('createFolder')->with($this->equalTo('newFolder'), $this->equalTo('/someFolder/'))->will($this->returnValue(true));
+        $mockedDriver->expects($this->once())->method('createFolder')->with(
+            $this->equalTo('newFolder'),
+            $this->equalTo('/someFolder/')
+        )->will($this->returnValue(true));
         $mockedDriver->expects($this->once())->method('folderExists')->with($this->equalTo('/someFolder/'))->will($this->returnValue(true));
         $this->subject->createFolder('newFolder', $mockedParentFolder);
     }
@@ -646,7 +717,7 @@ class ResourceStorageTest extends BaseTestCase
      * @test
      * @TODO: Rewrite or move to functional suite
      */
-    public function createFolderCanRecursivelyCreateFolders()
+    public function createFolderCanRecursivelyCreateFolders(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $this->addToMount(['someFolder' => []]);
@@ -663,7 +734,7 @@ class ResourceStorageTest extends BaseTestCase
      * @test
      * @TODO: Rewrite or move to functional suite
      */
-    public function createFolderUsesRootFolderAsParentFolderIfNotGiven()
+    public function createFolderUsesRootFolderAsParentFolderIfNotGiven(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $this->prepareSubject([], true);
@@ -677,7 +748,7 @@ class ResourceStorageTest extends BaseTestCase
      * @test
      * @TODO: Rewrite or move to functional suite
      */
-    public function createFolderCreatesNestedStructureEvenIfPartsAlreadyExist()
+    public function createFolderCreatesNestedStructureEvenIfPartsAlreadyExist(): void
     {
         $this->markTestSkipped('This test does way to much and is mocked incomplete. Skipped for now.');
         $this->addToMount([
@@ -695,7 +766,7 @@ class ResourceStorageTest extends BaseTestCase
     /**
      * @test
      */
-    public function createFolderThrowsExceptionIfParentFolderDoesNotExist()
+    public function createFolderThrowsExceptionIfParentFolderDoesNotExist(): void
     {
         $this->expectException(\InvalidArgumentException::class);
         $this->expectExceptionCode(1325689164);
@@ -705,4 +776,97 @@ class ResourceStorageTest extends BaseTestCase
         $mockedDriver->expects($this->once())->method('folderExists')->with($this->equalTo('/someFolder/'))->will($this->returnValue(false));
         $this->subject->createFolder('newFolder', $mockedParentFolder);
     }
+
+    /**
+     * @test
+     */
+    public function renameFileRenamesFileAsRequested(): void
+    {
+        $mockedDriver = $this->createDriverMock([], $this->subject);
+        $mockedDriver->expects($this->once())->method('renameFile')->will($this->returnValue('bar'));
+        $this->prepareSubject([], true, $mockedDriver, [], ['emitPreFileRenameSignal', 'emitPostFileRenameSignal']);
+        /** @var File $file */
+        $file = new File(['identifier' => 'foo', 'name' => 'foo'], $this->subject);
+        $result = $this->subject->renameFile($file, 'bar');
+        // fake what the indexer does in updateIndexEntry
+        $result->updateProperties(['name' => $result->getIdentifier()]);
+        $this->assertSame('bar', $result->getName());
+    }
+
+    /**
+     * @test
+     */
+    public function renameFileRenamesWithUniqueNameIfConflictAndConflictModeIsRename(): void
+    {
+        $mockedDriver = $this->createDriverMock([], $this->subject);
+        $mockedDriver->expects($this->any())->method('renameFile')->will($this->onConsecutiveCalls($this->throwException(new ExistingTargetFileNameException(
+            'foo',
+            1489593090
+        )), 'bar_01'));
+        //$mockedDriver->expects($this->at(1))->method('renameFile')->will($this->returnValue('bar_01'));
+        $mockedDriver->expects($this->any())->method('sanitizeFileName')->will($this->onConsecutiveCalls(
+            'bar',
+            'bar_01'
+        ));
+        $this->prepareSubject(
+            [],
+            true,
+            $mockedDriver,
+            [],
+            ['emitPreFileRenameSignal', 'emitPostFileRenameSignal', 'getUniqueName']
+        );
+        /** @var File $file */
+        $file = new File(['identifier' => 'foo', 'name' => 'foo'], $this->subject);
+        $this->subject->expects($this->once())->method('getUniqueName')->will($this->returnValue('bar_01'));
+        $result = $this->subject->renameFile($file, 'bar');
+        // fake what the indexer does in updateIndexEntry
+        $result->updateProperties(['name' => $result->getIdentifier()]);
+        $this->assertSame('bar_01', $result->getName());
+    }
+
+    /**
+     * @test
+     */
+    public function renameFileThrowsExceptionIfConflictAndConflictModeIsCancel(): void
+    {
+        $mockedDriver = $this->createDriverMock([], $this->subject);
+        $mockedDriver->expects($this->once())->method('renameFile')->will($this->throwException(new ExistingTargetFileNameException(
+            'foo',
+            1489593099
+        )));
+        $this->prepareSubject([], true, $mockedDriver, [], ['emitPreFileRenameSignal', 'emitPostFileRenameSignal']);
+        /** @var File $file */
+        $file = new File(['identifier' => 'foo', 'name' => 'foo'], $this->subject);
+        $this->expectException(ExistingTargetFileNameException::class);
+        $this->subject->renameFile($file, 'bar', DuplicationBehavior::CANCEL);
+    }
+
+    /**
+     * @test
+     */
+    public function renameFileReplacesIfConflictAndConflictModeIsReplace(): void
+    {
+        $mockedDriver = $this->createDriverMock([], $this->subject);
+        $mockedDriver->expects($this->once())->method('renameFile')->will($this->throwException(new ExistingTargetFileNameException(
+            'foo',
+            1489593098
+        )));
+        $mockedDriver->expects($this->any())->method('sanitizeFileName')->will($this->returnValue('bar'));
+        $this->prepareSubject([], true, $mockedDriver, [], [
+            'emitPreFileRenameSignal',
+            'emitPostFileRenameSignal',
+            'replaceFile',
+            'getPublicUrl',
+            'getResourceFactoryInstance'
+        ]);
+        $this->subject->expects($this->once())->method('getPublicUrl')->will($this->returnValue('somePath'));
+        $resourceFactory = $this->prophesize(ResourceFactory::class);
+        $file = $this->prophesize(FileInterface::class);
+        $resourceFactory->getFileObjectFromCombinedIdentifier(Argument::any())->willReturn($file->reveal());
+        $this->subject->expects($this->once())->method('replaceFile')->will($this->returnValue($file->reveal()));
+        $this->subject->expects($this->any())->method('getResourceFactoryInstance')->will(self::returnValue($resourceFactory->reveal()));
+        /** @var File $file */
+        $file = new File(['identifier' => 'foo', 'name' => 'foo', 'missing' => false], $this->subject);
+        $this->subject->renameFile($file, 'bar', DuplicationBehavior::REPLACE);
+    }
 }