aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Files/Storage/Wrapper
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Files/Storage/Wrapper')
-rw-r--r--tests/lib/Files/Storage/Wrapper/AvailabilityTest.php158
-rw-r--r--tests/lib/Files/Storage/Wrapper/EncodingTest.php239
-rw-r--r--tests/lib/Files/Storage/Wrapper/EncryptionTest.php1029
-rw-r--r--tests/lib/Files/Storage/Wrapper/JailTest.php54
-rw-r--r--tests/lib/Files/Storage/Wrapper/KnownMtimeTest.php69
-rw-r--r--tests/lib/Files/Storage/Wrapper/PermissionsMaskTest.php180
-rw-r--r--tests/lib/Files/Storage/Wrapper/QuotaTest.php232
-rw-r--r--tests/lib/Files/Storage/Wrapper/WrapperTest.php40
8 files changed, 2001 insertions, 0 deletions
diff --git a/tests/lib/Files/Storage/Wrapper/AvailabilityTest.php b/tests/lib/Files/Storage/Wrapper/AvailabilityTest.php
new file mode 100644
index 00000000000..d890081cbb6
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/AvailabilityTest.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Files\Storage\Wrapper;
+
+use OC\Files\Cache\Storage as StorageCache;
+use OC\Files\Storage\Temporary;
+use OC\Files\Storage\Wrapper\Availability;
+use OCP\Files\StorageNotAvailableException;
+
+class AvailabilityTest extends \Test\TestCase {
+ /** @var \PHPUnit\Framework\MockObject\MockObject|StorageCache */
+ protected $storageCache;
+ /** @var \PHPUnit\Framework\MockObject\MockObject|Temporary */
+ protected $storage;
+ /** @var Availability */
+ protected $wrapper;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->storageCache = $this->createMock(StorageCache::class);
+
+ $this->storage = $this->createMock(Temporary::class);
+ $this->storage->expects($this->any())
+ ->method('getStorageCache')
+ ->willReturn($this->storageCache);
+
+ $this->wrapper = new Availability(['storage' => $this->storage]);
+ }
+
+ /**
+ * Storage is available
+ */
+ public function testAvailable(): void {
+ $this->storage->expects($this->once())
+ ->method('getAvailability')
+ ->willReturn(['available' => true, 'last_checked' => 0]);
+ $this->storage->expects($this->never())
+ ->method('test');
+ $this->storage->expects($this->once())
+ ->method('mkdir');
+
+ $this->wrapper->mkdir('foobar');
+ }
+
+ /**
+ * Storage marked unavailable, TTL not expired
+ *
+ */
+ public function testUnavailable(): void {
+ $this->expectException(StorageNotAvailableException::class);
+
+ $this->storage->expects($this->once())
+ ->method('getAvailability')
+ ->willReturn(['available' => false, 'last_checked' => time()]);
+ $this->storage->expects($this->never())
+ ->method('test');
+ $this->storage->expects($this->never())
+ ->method('mkdir');
+
+ $this->wrapper->mkdir('foobar');
+ }
+
+ /**
+ * Storage marked unavailable, TTL expired
+ */
+ public function testUnavailableRecheck(): void {
+ $this->storage->expects($this->once())
+ ->method('getAvailability')
+ ->willReturn(['available' => false, 'last_checked' => 0]);
+ $this->storage->expects($this->once())
+ ->method('test')
+ ->willReturn(true);
+ $calls = [
+ false, // prevents concurrent rechecks
+ true, // sets correct availability
+ ];
+ $this->storage->expects($this->exactly(2))
+ ->method('setAvailability')
+ ->willReturnCallback(function ($value) use (&$calls): void {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, $value);
+ });
+ $this->storage->expects($this->once())
+ ->method('mkdir');
+
+ $this->wrapper->mkdir('foobar');
+ }
+
+ /**
+ * Storage marked available, but throws StorageNotAvailableException
+ *
+ */
+ public function testAvailableThrowStorageNotAvailable(): void {
+ $this->expectException(StorageNotAvailableException::class);
+
+ $this->storage->expects($this->once())
+ ->method('getAvailability')
+ ->willReturn(['available' => true, 'last_checked' => 0]);
+ $this->storage->expects($this->never())
+ ->method('test');
+ $this->storage->expects($this->once())
+ ->method('mkdir')
+ ->willThrowException(new StorageNotAvailableException());
+ $this->storageCache->expects($this->once())
+ ->method('setAvailability')
+ ->with($this->equalTo(false));
+
+ $this->wrapper->mkdir('foobar');
+ }
+
+ /**
+ * Storage available, but call fails
+ * Method failure does not indicate storage unavailability
+ */
+ public function testAvailableFailure(): void {
+ $this->storage->expects($this->once())
+ ->method('getAvailability')
+ ->willReturn(['available' => true, 'last_checked' => 0]);
+ $this->storage->expects($this->never())
+ ->method('test');
+ $this->storage->expects($this->once())
+ ->method('mkdir')
+ ->willReturn(false);
+ $this->storage->expects($this->never())
+ ->method('setAvailability');
+
+ $this->wrapper->mkdir('foobar');
+ }
+
+ /**
+ * Storage available, but throws exception
+ * Standard exception does not indicate storage unavailability
+ *
+ */
+ public function testAvailableThrow(): void {
+ $this->expectException(\Exception::class);
+
+ $this->storage->expects($this->once())
+ ->method('getAvailability')
+ ->willReturn(['available' => true, 'last_checked' => 0]);
+ $this->storage->expects($this->never())
+ ->method('test');
+ $this->storage->expects($this->once())
+ ->method('mkdir')
+ ->willThrowException(new \Exception());
+ $this->storage->expects($this->never())
+ ->method('setAvailability');
+
+ $this->wrapper->mkdir('foobar');
+ }
+}
diff --git a/tests/lib/Files/Storage/Wrapper/EncodingTest.php b/tests/lib/Files/Storage/Wrapper/EncodingTest.php
new file mode 100644
index 00000000000..cb6b6de0fb7
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/EncodingTest.php
@@ -0,0 +1,239 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Storage\Wrapper;
+
+use OC\Files\Storage\Temporary;
+use OC\Files\Storage\Wrapper\Encoding;
+
+class EncodingTest extends \Test\Files\Storage\Storage {
+ public const NFD_NAME = 'ümlaut';
+ public const NFC_NAME = 'ümlaut';
+
+ /**
+ * @var Temporary
+ */
+ private $sourceStorage;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->sourceStorage = new Temporary([]);
+ $this->instance = new Encoding([
+ 'storage' => $this->sourceStorage
+ ]);
+ }
+
+ protected function tearDown(): void {
+ $this->sourceStorage->cleanUp();
+ parent::tearDown();
+ }
+
+ public static function directoryProvider(): array {
+ $a = parent::directoryProvider();
+ $a[] = [self::NFC_NAME];
+ return $a;
+ }
+
+ public static function fileNameProvider(): array {
+ $a = parent::fileNameProvider();
+ $a[] = [self::NFD_NAME . '.txt'];
+ return $a;
+ }
+
+ public static function copyAndMoveProvider(): array {
+ $a = parent::copyAndMoveProvider();
+ $a[] = [self::NFD_NAME . '.txt', self::NFC_NAME . '-renamed.txt'];
+ return $a;
+ }
+
+ public static function accessNameProvider(): array {
+ return [
+ [self::NFD_NAME],
+ [self::NFC_NAME],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('accessNameProvider')]
+ public function testFputEncoding($accessName): void {
+ $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar');
+ $this->assertEquals('bar', $this->instance->file_get_contents($accessName));
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('accessNameProvider')]
+ public function testFopenReadEncoding($accessName): void {
+ $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar');
+ $fh = $this->instance->fopen($accessName, 'r');
+ $data = fgets($fh);
+ fclose($fh);
+ $this->assertEquals('bar', $data);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('accessNameProvider')]
+ public function testFopenOverwriteEncoding($accessName): void {
+ $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar');
+ $fh = $this->instance->fopen($accessName, 'w');
+ $data = fputs($fh, 'test');
+ fclose($fh);
+ $data = $this->sourceStorage->file_get_contents(self::NFD_NAME);
+ $this->assertEquals('test', $data);
+ $this->assertFalse($this->sourceStorage->file_exists(self::NFC_NAME));
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('accessNameProvider')]
+ public function testFileExistsEncoding($accessName): void {
+ $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar');
+ $this->assertTrue($this->instance->file_exists($accessName));
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('accessNameProvider')]
+ public function testUnlinkEncoding($accessName): void {
+ $this->sourceStorage->file_put_contents(self::NFD_NAME, 'bar');
+ $this->assertTrue($this->instance->unlink($accessName));
+ $this->assertFalse($this->sourceStorage->file_exists(self::NFC_NAME));
+ $this->assertFalse($this->sourceStorage->file_exists(self::NFD_NAME));
+ }
+
+ public function testNfcHigherPriority(): void {
+ $this->sourceStorage->file_put_contents(self::NFC_NAME, 'nfc');
+ $this->sourceStorage->file_put_contents(self::NFD_NAME, 'nfd');
+ $this->assertEquals('nfc', $this->instance->file_get_contents(self::NFC_NAME));
+ }
+
+ public static function encodedDirectoriesProvider(): array {
+ return [
+ [self::NFD_NAME, self::NFC_NAME],
+ [self::NFD_NAME . '/' . self::NFD_NAME, self::NFC_NAME . '/' . self::NFC_NAME],
+ [self::NFD_NAME . '/' . self::NFC_NAME . '/' . self::NFD_NAME, self::NFC_NAME . '/' . self::NFC_NAME . '/' . self::NFC_NAME],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('encodedDirectoriesProvider')]
+ public function testOperationInsideDirectory($sourceDir, $accessDir): void {
+ $this->sourceStorage->mkdir($sourceDir);
+ $this->instance->file_put_contents($accessDir . '/test.txt', 'bar');
+ $this->assertTrue($this->instance->file_exists($accessDir . '/test.txt'));
+ $this->assertEquals('bar', $this->instance->file_get_contents($accessDir . '/test.txt'));
+
+ $this->sourceStorage->file_put_contents($sourceDir . '/' . self::NFD_NAME, 'foo');
+ $this->assertTrue($this->instance->file_exists($accessDir . '/' . self::NFC_NAME));
+ $this->assertEquals('foo', $this->instance->file_get_contents($accessDir . '/' . self::NFC_NAME));
+
+ // try again to make it use cached path
+ $this->assertEquals('bar', $this->instance->file_get_contents($accessDir . '/test.txt'));
+ $this->assertTrue($this->instance->file_exists($accessDir . '/test.txt'));
+ $this->assertEquals('foo', $this->instance->file_get_contents($accessDir . '/' . self::NFC_NAME));
+ $this->assertTrue($this->instance->file_exists($accessDir . '/' . self::NFC_NAME));
+ }
+
+ public function testCacheExtraSlash(): void {
+ $this->sourceStorage->file_put_contents(self::NFD_NAME, 'foo');
+ $this->assertEquals(3, $this->instance->file_put_contents(self::NFC_NAME, 'bar'));
+ $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME));
+ clearstatcache();
+ $this->assertEquals(5, $this->instance->file_put_contents('/' . self::NFC_NAME, 'baric'));
+ $this->assertEquals('baric', $this->instance->file_get_contents(self::NFC_NAME));
+ clearstatcache();
+ $this->assertEquals(8, $this->instance->file_put_contents('/' . self::NFC_NAME, 'barbaric'));
+ $this->assertEquals('barbaric', $this->instance->file_get_contents('//' . self::NFC_NAME));
+ }
+
+ public static function sourceAndTargetDirectoryProvider(): array {
+ return [
+ [self::NFC_NAME . '1', self::NFC_NAME . '2'],
+ [self::NFD_NAME . '1', self::NFC_NAME . '2'],
+ [self::NFC_NAME . '1', self::NFD_NAME . '2'],
+ [self::NFD_NAME . '1', self::NFD_NAME . '2'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('sourceAndTargetDirectoryProvider')]
+ public function testCopyAndMoveEncodedFolder($sourceDir, $targetDir): void {
+ $this->sourceStorage->mkdir($sourceDir);
+ $this->sourceStorage->mkdir($targetDir);
+ $this->sourceStorage->file_put_contents($sourceDir . '/test.txt', 'bar');
+ $this->assertTrue($this->instance->copy(self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test.txt'));
+
+ $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '1/test.txt'));
+ $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test.txt'));
+ $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test.txt'));
+
+ $this->assertTrue($this->instance->rename(self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test2.txt'));
+ $this->assertFalse($this->instance->file_exists(self::NFC_NAME . '1/test.txt'));
+ $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test2.txt'));
+
+ $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test2.txt'));
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('sourceAndTargetDirectoryProvider')]
+ public function testCopyAndMoveFromStorageEncodedFolder($sourceDir, $targetDir): void {
+ $this->sourceStorage->mkdir($sourceDir);
+ $this->sourceStorage->mkdir($targetDir);
+ $this->sourceStorage->file_put_contents($sourceDir . '/test.txt', 'bar');
+ $this->assertTrue($this->instance->copyFromStorage($this->instance, self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test.txt'));
+
+ $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '1/test.txt'));
+ $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test.txt'));
+ $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test.txt'));
+
+ $this->assertTrue($this->instance->moveFromStorage($this->instance, self::NFC_NAME . '1/test.txt', self::NFC_NAME . '2/test2.txt'));
+ $this->assertFalse($this->instance->file_exists(self::NFC_NAME . '1/test.txt'));
+ $this->assertTrue($this->instance->file_exists(self::NFC_NAME . '2/test2.txt'));
+
+ $this->assertEquals('bar', $this->instance->file_get_contents(self::NFC_NAME . '2/test2.txt'));
+ }
+
+ public function testNormalizedDirectoryEntriesOpenDir(): void {
+ $this->sourceStorage->mkdir('/test');
+ $this->sourceStorage->mkdir('/test/' . self::NFD_NAME);
+
+ $this->assertTrue($this->instance->file_exists('/test/' . self::NFC_NAME));
+ $this->assertTrue($this->instance->file_exists('/test/' . self::NFD_NAME));
+
+ $dh = $this->instance->opendir('/test');
+ $content = [];
+ while (($file = readdir($dh)) !== false) {
+ if ($file != '.' and $file != '..') {
+ $content[] = $file;
+ }
+ }
+
+ $this->assertCount(1, $content);
+ $this->assertEquals(self::NFC_NAME, $content[0]);
+ }
+
+ public function testNormalizedDirectoryEntriesGetDirectoryContent(): void {
+ $this->sourceStorage->mkdir('/test');
+ $this->sourceStorage->mkdir('/test/' . self::NFD_NAME);
+
+ $this->assertTrue($this->instance->file_exists('/test/' . self::NFC_NAME));
+ $this->assertTrue($this->instance->file_exists('/test/' . self::NFD_NAME));
+
+ $content = iterator_to_array($this->instance->getDirectoryContent('/test'));
+ $this->assertCount(1, $content);
+ $this->assertEquals(self::NFC_NAME, $content[0]['name']);
+ }
+
+ public function testNormalizedGetMetaData(): void {
+ $this->sourceStorage->mkdir('/test');
+ $this->sourceStorage->mkdir('/test/' . self::NFD_NAME);
+
+ $entry = $this->instance->getMetaData('/test/' . self::NFC_NAME);
+ $this->assertEquals(self::NFC_NAME, $entry['name']);
+
+ $entry = $this->instance->getMetaData('/test/' . self::NFD_NAME);
+ $this->assertEquals(self::NFC_NAME, $entry['name']);
+ }
+
+ /**
+ * Regression test of https://github.com/nextcloud/server/issues/50431
+ */
+ public function testNoMetadata() {
+ $this->assertNull($this->instance->getMetaData('/test/null'));
+ }
+
+}
diff --git a/tests/lib/Files/Storage/Wrapper/EncryptionTest.php b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php
new file mode 100644
index 00000000000..3e643714300
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/EncryptionTest.php
@@ -0,0 +1,1029 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Files\Storage\Wrapper;
+
+use Exception;
+use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
+use OC\Encryption\File;
+use OC\Encryption\Util;
+use OC\Files\Cache\Cache;
+use OC\Files\Cache\CacheEntry;
+use OC\Files\Mount\MountPoint;
+use OC\Files\Storage\Temporary;
+use OC\Files\Storage\Wrapper\Encryption;
+use OC\Files\View;
+use OC\Memcache\ArrayCache;
+use OC\User\Manager;
+use OCP\Encryption\IEncryptionModule;
+use OCP\Encryption\IFile;
+use OCP\Encryption\Keys\IStorage;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Mount\IMountPoint;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\ITempManager;
+use OCP\Server;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\Files\Storage\Storage;
+
+class EncryptionTest extends Storage {
+ /**
+ * block size will always be 8192 for a PHP stream
+ * @see https://bugs.php.net/bug.php?id=21641
+ */
+ protected int $headerSize = 8192;
+ private Temporary $sourceStorage;
+ /** @var Encryption&MockObject */
+ protected $instance;
+ private \OC\Encryption\Keys\Storage&MockObject $keyStore;
+ private Util&MockObject $util;
+ private \OC\Encryption\Manager&MockObject $encryptionManager;
+ private IEncryptionModule&MockObject $encryptionModule;
+ private Cache&MockObject $cache;
+ private LoggerInterface&MockObject $logger;
+ private File&MockObject $file;
+ private MountPoint&MockObject $mount;
+ private \OC\Files\Mount\Manager&MockObject $mountManager;
+ private \OC\Group\Manager&MockObject $groupManager;
+ private IConfig&MockObject $config;
+ private ArrayCache&MockObject $arrayCache;
+ /** dummy unencrypted size */
+ private int $dummySize = -1;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $mockModule = $this->buildMockModule();
+ $this->encryptionManager = $this->getMockBuilder(\OC\Encryption\Manager::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getEncryptionModule', 'isEnabled'])
+ ->getMock();
+ $this->encryptionManager->expects($this->any())
+ ->method('getEncryptionModule')
+ ->willReturn($mockModule);
+
+ $this->arrayCache = $this->createMock(ArrayCache::class);
+ $this->config = $this->getMockBuilder(IConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->groupManager = $this->getMockBuilder('\OC\Group\Manager')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->util = $this->getMockBuilder(Util::class)
+ ->onlyMethods(['getUidAndFilename', 'isFile', 'isExcluded', 'stripPartialFileExtension'])
+ ->setConstructorArgs([new View(), new Manager(
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ), $this->groupManager, $this->config, $this->arrayCache])
+ ->getMock();
+ $this->util->expects($this->any())
+ ->method('getUidAndFilename')
+ ->willReturnCallback(function ($path) {
+ return ['user1', $path];
+ });
+ $this->util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnCallback(function ($path) {
+ return $path;
+ });
+
+ $this->file = $this->getMockBuilder(File::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getAccessList'])
+ ->getMock();
+ $this->file->expects($this->any())->method('getAccessList')->willReturn([]);
+
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->sourceStorage = new Temporary([]);
+
+ $this->keyStore = $this->createMock(\OC\Encryption\Keys\Storage::class);
+
+ $this->mount = $this->getMockBuilder(MountPoint::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getOption'])
+ ->getMock();
+ $this->mount->expects($this->any())->method('getOption')->willReturnCallback(function ($option, $default) {
+ if ($option === 'encrypt' && $default === true) {
+ global $mockedMountPointEncryptionEnabled;
+ if ($mockedMountPointEncryptionEnabled !== null) {
+ return $mockedMountPointEncryptionEnabled;
+ }
+ }
+ return true;
+ });
+
+ $this->cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+ $this->cache->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function ($path) {
+ return ['encrypted' => false, 'path' => $path];
+ });
+
+ $this->mountManager = $this->createMock(\OC\Files\Mount\Manager::class);
+ $this->mountManager->method('findByStorageId')
+ ->willReturn([]);
+
+ $this->instance = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $this->sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $this->util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache
+ ]
+ )
+ ->onlyMethods(['getMetaData', 'getCache', 'getEncryptionModule'])
+ ->getMock();
+
+ $this->instance->expects($this->any())
+ ->method('getMetaData')
+ ->willReturnCallback(function ($path) {
+ return ['encrypted' => true, 'size' => $this->dummySize, 'path' => $path];
+ });
+
+ $this->instance->expects($this->any())
+ ->method('getCache')
+ ->willReturn($this->cache);
+
+ $this->instance->expects($this->any())
+ ->method('getEncryptionModule')
+ ->willReturn($mockModule);
+ }
+
+ protected function buildMockModule(): IEncryptionModule&MockObject {
+ $this->encryptionModule = $this->getMockBuilder('\OCP\Encryption\IEncryptionModule')
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getId', 'getDisplayName', 'begin', 'end', 'encrypt', 'decrypt', 'update', 'shouldEncrypt', 'getUnencryptedBlockSize', 'isReadable', 'encryptAll', 'prepareDecryptAll', 'isReadyForUser', 'needDetailedAccessList'])
+ ->getMock();
+
+ $this->encryptionModule->expects($this->any())->method('getId')->willReturn('UNIT_TEST_MODULE');
+ $this->encryptionModule->expects($this->any())->method('getDisplayName')->willReturn('Unit test module');
+ $this->encryptionModule->expects($this->any())->method('begin')->willReturn([]);
+ $this->encryptionModule->expects($this->any())->method('end')->willReturn('');
+ $this->encryptionModule->expects($this->any())->method('encrypt')->willReturnArgument(0);
+ $this->encryptionModule->expects($this->any())->method('decrypt')->willReturnArgument(0);
+ $this->encryptionModule->expects($this->any())->method('update')->willReturn(true);
+ $this->encryptionModule->expects($this->any())->method('shouldEncrypt')->willReturn(true);
+ $this->encryptionModule->expects($this->any())->method('getUnencryptedBlockSize')->willReturn(8192);
+ $this->encryptionModule->expects($this->any())->method('isReadable')->willReturn(true);
+ $this->encryptionModule->expects($this->any())->method('needDetailedAccessList')->willReturn(false);
+ return $this->encryptionModule;
+ }
+
+ /**
+ *
+ * @param string $path
+ * @param array $metaData
+ * @param bool $encrypted
+ * @param bool $unencryptedSizeSet
+ * @param int $storedUnencryptedSize
+ * @param array $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetMetaData')]
+ public function testGetMetaData($path, $metaData, $encrypted, $unencryptedSizeSet, $storedUnencryptedSize, $expected): void {
+ $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()->getMock();
+
+ $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+ $cache->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(
+ function ($path) use ($encrypted) {
+ return new CacheEntry(['encrypted' => $encrypted, 'path' => $path, 'size' => 0, 'fileid' => 1]);
+ }
+ );
+
+ $this->instance = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $this->util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache,
+ ]
+ )
+ ->onlyMethods(['getCache', 'verifyUnencryptedSize'])
+ ->getMock();
+
+ if ($unencryptedSizeSet) {
+ $this->invokePrivate($this->instance, 'unencryptedSize', [[$path => $storedUnencryptedSize]]);
+ }
+
+ $fileEntry = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+ $sourceStorage->expects($this->once())->method('getMetaData')->with($path)
+ ->willReturn($metaData);
+ $sourceStorage->expects($this->any())
+ ->method('getCache')
+ ->with($path)
+ ->willReturn($fileEntry);
+ if ($metaData !== null) {
+ $fileEntry->expects($this->any())
+ ->method('get')
+ ->with($metaData['fileid']);
+ }
+
+ $this->instance->expects($this->any())->method('getCache')->willReturn($cache);
+ if ($expected !== null) {
+ $this->instance->expects($this->any())->method('verifyUnencryptedSize')
+ ->with($path, 0)->willReturn($expected['size']);
+ }
+
+ $result = $this->instance->getMetaData($path);
+ if (isset($expected['encrypted'])) {
+ $this->assertSame($expected['encrypted'], (bool)$result['encrypted']);
+
+ if (isset($expected['encryptedVersion'])) {
+ $this->assertSame($expected['encryptedVersion'], $result['encryptedVersion']);
+ }
+ }
+
+ if ($expected !== null) {
+ $this->assertSame($expected['size'], $result['size']);
+ } else {
+ $this->assertSame(null, $result);
+ }
+ }
+
+ public static function dataTestGetMetaData(): array {
+ return [
+ ['/test.txt', ['size' => 42, 'encrypted' => 2, 'encryptedVersion' => 2, 'fileid' => 1], true, true, 12, ['size' => 12, 'encrypted' => true, 'encryptedVersion' => 2]],
+ ['/test.txt', null, true, true, 12, null],
+ ['/test.txt', ['size' => 42, 'encrypted' => 0, 'fileid' => 1], false, false, 12, ['size' => 42, 'encrypted' => false]],
+ ['/test.txt', ['size' => 42, 'encrypted' => false, 'fileid' => 1], true, false, 12, ['size' => 12, 'encrypted' => true]]
+ ];
+ }
+
+ public function testFilesize(): void {
+ $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+ $cache->expects($this->any())
+ ->method('get')
+ ->willReturn(new CacheEntry(['encrypted' => true, 'path' => '/test.txt', 'size' => 0, 'fileid' => 1]));
+
+ $this->instance = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $this->sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $this->util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache,
+ ]
+ )
+ ->onlyMethods(['getCache', 'verifyUnencryptedSize'])
+ ->getMock();
+
+ $this->instance->expects($this->any())->method('getCache')->willReturn($cache);
+ $this->instance->expects($this->any())->method('verifyUnencryptedSize')
+ ->willReturn(42);
+
+
+ $this->assertSame(42,
+ $this->instance->filesize('/test.txt')
+ );
+ }
+
+ /**
+ *
+ * @param int $encryptedSize
+ * @param int $unencryptedSize
+ * @param bool $failure
+ * @param int $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestVerifyUnencryptedSize')]
+ public function testVerifyUnencryptedSize($encryptedSize, $unencryptedSize, $failure, $expected): void {
+ $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()->getMock();
+
+ $this->instance = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $this->util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache,
+ ]
+ )
+ ->onlyMethods(['fixUnencryptedSize'])
+ ->getMock();
+
+ $sourceStorage->expects($this->once())->method('filesize')->willReturn($encryptedSize);
+
+ $this->instance->expects($this->any())->method('fixUnencryptedSize')
+ ->with('/test.txt', $encryptedSize, $unencryptedSize)
+ ->willReturnCallback(
+ function () use ($failure, $expected) {
+ if ($failure) {
+ throw new Exception();
+ } else {
+ return $expected;
+ }
+ }
+ );
+
+ $this->assertSame(
+ $expected,
+ $this->invokePrivate($this->instance, 'verifyUnencryptedSize', ['/test.txt', $unencryptedSize])
+ );
+ }
+
+ public static function dataTestVerifyUnencryptedSize(): array {
+ return [
+ [120, 80, false, 80],
+ [120, 120, false, 80],
+ [120, -1, false, 80],
+ [120, -1, true, -1]
+ ];
+ }
+
+ /**
+ *
+ * @param string $source
+ * @param string $target
+ * @param $encryptionEnabled
+ * @param boolean $renameKeysReturn
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCopyAndRename')]
+ public function testRename($source,
+ $target,
+ $encryptionEnabled,
+ $renameKeysReturn): void {
+ if ($encryptionEnabled) {
+ $this->keyStore
+ ->expects($this->once())
+ ->method('renameKeys')
+ ->willReturn($renameKeysReturn);
+ } else {
+ $this->keyStore
+ ->expects($this->never())->method('renameKeys');
+ }
+ $this->util->expects($this->any())
+ ->method('isFile')->willReturn(true);
+ $this->encryptionManager->expects($this->once())
+ ->method('isEnabled')->willReturn($encryptionEnabled);
+
+ $this->instance->mkdir($source);
+ $this->instance->mkdir(dirname($target));
+ $this->instance->rename($source, $target);
+ }
+
+ public function testCopyEncryption(): void {
+ $this->instance->file_put_contents('source.txt', 'bar');
+ $this->instance->copy('source.txt', 'target.txt');
+ $this->assertSame('bar', $this->instance->file_get_contents('target.txt'));
+ $targetMeta = $this->instance->getMetaData('target.txt');
+ $sourceMeta = $this->instance->getMetaData('source.txt');
+ $this->assertSame($sourceMeta['encrypted'], $targetMeta['encrypted']);
+ $this->assertSame($sourceMeta['size'], $targetMeta['size']);
+ }
+
+ /**
+ * data provider for testCopyTesting() and dataTestCopyAndRename()
+ *
+ * @return array
+ */
+ public static function dataTestCopyAndRename(): array {
+ return [
+ ['source', 'target', true, false, false],
+ ['source', 'target', true, true, false],
+ ['source', '/subFolder/target', true, false, false],
+ ['source', '/subFolder/target', true, true, true],
+ ['source', '/subFolder/target', false, true, false],
+ ];
+ }
+
+ public function testIsLocal(): void {
+ $this->encryptionManager->expects($this->once())
+ ->method('isEnabled')->willReturn(true);
+ $this->assertFalse($this->instance->isLocal());
+ }
+
+ /**
+ *
+ * @param string $path
+ * @param boolean $rmdirResult
+ * @param boolean $isExcluded
+ * @param boolean $encryptionEnabled
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestRmdir')]
+ public function testRmdir($path, $rmdirResult, $isExcluded, $encryptionEnabled): void {
+ $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()->getMock();
+
+ $util = $this->getMockBuilder('\OC\Encryption\Util')->disableOriginalConstructor()->getMock();
+
+ $sourceStorage->expects($this->once())->method('rmdir')->willReturn($rmdirResult);
+ $util->expects($this->any())->method('isExcluded')->willReturn($isExcluded);
+ $this->encryptionManager->expects($this->any())->method('isEnabled')->willReturn($encryptionEnabled);
+
+ $encryptionStorage = new Encryption(
+ [
+ 'storage' => $sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/mountPoint',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache,
+ );
+
+
+ if ($rmdirResult === true && $isExcluded === false && $encryptionEnabled === true) {
+ $this->keyStore->expects($this->once())->method('deleteAllFileKeys')->with('/mountPoint' . $path);
+ } else {
+ $this->keyStore->expects($this->never())->method('deleteAllFileKeys');
+ }
+
+ $encryptionStorage->rmdir($path);
+ }
+
+ public static function dataTestRmdir(): array {
+ return [
+ ['/file.txt', true, true, true],
+ ['/file.txt', false, true, true],
+ ['/file.txt', true, false, true],
+ ['/file.txt', false, false, true],
+ ['/file.txt', true, true, false],
+ ['/file.txt', false, true, false],
+ ['/file.txt', true, false, false],
+ ['/file.txt', false, false, false],
+ ];
+ }
+
+ /**
+ *
+ * @param boolean $excluded
+ * @param boolean $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCopyKeys')]
+ public function testCopyKeys($excluded, $expected): void {
+ $this->util->expects($this->once())
+ ->method('isExcluded')
+ ->willReturn($excluded);
+
+ if ($excluded) {
+ $this->keyStore->expects($this->never())->method('copyKeys');
+ } else {
+ $this->keyStore->expects($this->once())->method('copyKeys')->willReturn(true);
+ }
+
+ $this->assertSame($expected,
+ self::invokePrivate($this->instance, 'copyKeys', ['/source', '/target'])
+ );
+ }
+
+ public static function dataTestCopyKeys(): array {
+ return [
+ [true, false],
+ [false, true],
+ ];
+ }
+
+ /**
+ *
+ * @param string $path
+ * @param bool $strippedPathExists
+ * @param string $strippedPath
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetHeader')]
+ public function testGetHeader($path, $strippedPathExists, $strippedPath): void {
+ $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()->getMock();
+
+ $util = $this->getMockBuilder('\OC\Encryption\Util')
+ ->setConstructorArgs(
+ [
+ new View(),
+ new Manager(
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ),
+ $this->groupManager,
+ $this->config,
+ $this->arrayCache
+ ]
+ )->getMock();
+
+ $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+ $cache->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function ($path) {
+ return ['encrypted' => true, 'path' => $path];
+ });
+
+ $instance = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache,
+ ]
+ )
+ ->onlyMethods(['getCache', 'readFirstBlock'])
+ ->getMock();
+
+ $instance->method('getCache')->willReturn($cache);
+
+ $util->method('parseRawHeader')
+ ->willReturn([Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']);
+
+ if ($strippedPathExists) {
+ $instance->method('readFirstBlock')
+ ->with($strippedPath)->willReturn('');
+ } else {
+ $instance->method('readFirstBlock')
+ ->with($path)->willReturn('');
+ }
+
+ $util->expects($this->once())->method('stripPartialFileExtension')
+ ->with($path)->willReturn($strippedPath);
+ $sourceStorage->expects($this->once())
+ ->method('is_file')
+ ->with($strippedPath)
+ ->willReturn($strippedPathExists);
+
+ $this->invokePrivate($instance, 'getHeader', [$path]);
+ }
+
+ public static function dataTestGetHeader(): array {
+ return [
+ ['/foo/bar.txt', false, '/foo/bar.txt'],
+ ['/foo/bar.txt.part', false, '/foo/bar.txt'],
+ ['/foo/bar.txt.ocTransferId7437493.part', false, '/foo/bar.txt'],
+ ['/foo/bar.txt.part', true, '/foo/bar.txt'],
+ ['/foo/bar.txt.ocTransferId7437493.part', true, '/foo/bar.txt'],
+ ];
+ }
+
+ /**
+ * test if getHeader adds the default module correctly to the header for
+ * legacy files
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetHeaderAddLegacyModule')]
+ public function testGetHeaderAddLegacyModule($header, $isEncrypted, $strippedPathExists, $expected): void {
+ $sourceStorage = $this->getMockBuilder(\OC\Files\Storage\Storage::class)
+ ->disableOriginalConstructor()->getMock();
+
+ $sourceStorage->expects($this->once())
+ ->method('is_file')
+ ->with('test.txt')
+ ->willReturn($strippedPathExists);
+
+ $util = $this->getMockBuilder(Util::class)
+ ->onlyMethods(['stripPartialFileExtension', 'parseRawHeader'])
+ ->setConstructorArgs([new View(), new Manager(
+ $this->config,
+ $this->createMock(ICacheFactory::class),
+ $this->createMock(IEventDispatcher::class),
+ $this->createMock(LoggerInterface::class),
+ ), $this->groupManager, $this->config, $this->arrayCache])
+ ->getMock();
+ $util->expects($this->any())
+ ->method('stripPartialFileExtension')
+ ->willReturnCallback(function ($path) {
+ return $path;
+ });
+
+ $cache = $this->createMock(Cache::class);
+ $cache->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function ($path) use ($isEncrypted) {
+ return ['encrypted' => $isEncrypted, 'path' => $path];
+ });
+
+ $instance = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache,
+ ]
+ )
+ ->onlyMethods(['readFirstBlock', 'getCache'])
+ ->getMock();
+
+ $instance->method('readFirstBlock')->willReturn('');
+
+ $util->method(('parseRawHeader'))->willReturn($header);
+ $instance->method('getCache')->willReturn($cache);
+
+ $result = $this->invokePrivate($instance, 'getHeader', ['test.txt']);
+ $this->assertSameSize($expected, $result);
+ foreach ($result as $key => $value) {
+ $this->assertArrayHasKey($key, $expected);
+ $this->assertSame($expected[$key], $value);
+ }
+ }
+
+ public static function dataTestGetHeaderAddLegacyModule(): array {
+ return [
+ [['cipher' => 'AES-128'], true, true, ['cipher' => 'AES-128', Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
+ [[], true, false, []],
+ [[], true, true, [Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
+ [[], false, true, []],
+ ];
+ }
+
+ public static function dataCopyBetweenStorage(): array {
+ return [
+ [true, true, true],
+ [true, false, false],
+ [false, true, false],
+ [false, false, false],
+ ];
+ }
+
+ public function testCopyBetweenStorageMinimumEncryptedVersion(): void {
+ $storage2 = $this->createMock(\OC\Files\Storage\Storage::class);
+
+ $sourceInternalPath = $targetInternalPath = 'file.txt';
+ $preserveMtime = $isRename = false;
+
+ $storage2->expects($this->any())
+ ->method('fopen')
+ ->willReturnCallback(function ($path, $mode) {
+ $temp = Server::get(ITempManager::class);
+ return fopen($temp->getTemporaryFile(), $mode);
+ });
+ $storage2->method('getId')
+ ->willReturn('stroage2');
+ $cache = $this->createMock(ICache::class);
+ $cache->expects($this->once())
+ ->method('get')
+ ->with($sourceInternalPath)
+ ->willReturn(['encryptedVersion' => 0]);
+ $storage2->expects($this->once())
+ ->method('getCache')
+ ->willReturn($cache);
+ $this->encryptionManager->expects($this->any())
+ ->method('isEnabled')
+ ->willReturn(true);
+ global $mockedMountPointEncryptionEnabled;
+ $mockedMountPointEncryptionEnabled = true;
+
+ $expectedCachePut = [
+ 'encrypted' => true,
+ ];
+ $expectedCachePut['encryptedVersion'] = 1;
+
+ $this->cache->expects($this->once())
+ ->method('put')
+ ->with($sourceInternalPath, $expectedCachePut);
+
+ $this->invokePrivate($this->instance, 'copyBetweenStorage', [$storage2, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename]);
+
+ $this->assertFalse(false);
+ }
+
+ /**
+ *
+ * @param bool $encryptionEnabled
+ * @param bool $mountPointEncryptionEnabled
+ * @param bool $expectedEncrypted
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataCopyBetweenStorage')]
+ public function testCopyBetweenStorage($encryptionEnabled, $mountPointEncryptionEnabled, $expectedEncrypted): void {
+ $storage2 = $this->createMock(\OC\Files\Storage\Storage::class);
+
+ $sourceInternalPath = $targetInternalPath = 'file.txt';
+ $preserveMtime = $isRename = false;
+
+ $storage2->expects($this->any())
+ ->method('fopen')
+ ->willReturnCallback(function ($path, $mode) {
+ $temp = Server::get(ITempManager::class);
+ return fopen($temp->getTemporaryFile(), $mode);
+ });
+ $storage2->method('getId')
+ ->willReturn('stroage2');
+ if ($expectedEncrypted) {
+ $cache = $this->createMock(ICache::class);
+ $cache->expects($this->once())
+ ->method('get')
+ ->with($sourceInternalPath)
+ ->willReturn(['encryptedVersion' => 12345]);
+ $storage2->expects($this->once())
+ ->method('getCache')
+ ->willReturn($cache);
+ }
+ $this->encryptionManager->expects($this->any())
+ ->method('isEnabled')
+ ->willReturn($encryptionEnabled);
+ // FIXME can not overwrite the return after definition
+ // $this->mount->expects($this->at(0))
+ // ->method('getOption')
+ // ->with('encrypt', true)
+ // ->willReturn($mountPointEncryptionEnabled);
+ global $mockedMountPointEncryptionEnabled;
+ $mockedMountPointEncryptionEnabled = $mountPointEncryptionEnabled;
+
+ $expectedCachePut = [
+ 'encrypted' => $expectedEncrypted,
+ ];
+ if ($expectedEncrypted === true) {
+ $expectedCachePut['encryptedVersion'] = 1;
+ }
+
+ $this->arrayCache->expects($this->never())->method('set');
+
+ $this->cache->expects($this->once())
+ ->method('put')
+ ->with($sourceInternalPath, $expectedCachePut);
+
+ $this->invokePrivate($this->instance, 'copyBetweenStorage', [$storage2, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename]);
+
+ $this->assertFalse(false);
+ }
+
+ /**
+ *
+ * @param string $sourceInternalPath
+ * @param string $targetInternalPath
+ * @param bool $copyResult
+ * @param bool $encrypted
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCopyBetweenStorageVersions')]
+ public function testCopyBetweenStorageVersions($sourceInternalPath, $targetInternalPath, $copyResult, $encrypted): void {
+ $sourceStorage = $this->createMock(\OC\Files\Storage\Storage::class);
+
+ $targetStorage = $this->createMock(\OC\Files\Storage\Storage::class);
+
+ $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+
+ $mountPoint = '/mountPoint';
+
+ /** @var Encryption |MockObject $instance */
+ $instance = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $targetStorage,
+ 'root' => 'foo',
+ 'mountPoint' => $mountPoint,
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager,
+ $this->util,
+ $this->logger,
+ $this->file,
+ null,
+ $this->keyStore,
+ $this->mountManager,
+ $this->arrayCache
+ ]
+ )
+ ->onlyMethods(['updateUnencryptedSize', 'getCache'])
+ ->getMock();
+
+ $targetStorage->expects($this->once())->method('copyFromStorage')
+ ->with($sourceStorage, $sourceInternalPath, $targetInternalPath)
+ ->willReturn($copyResult);
+
+ $instance->expects($this->any())->method('getCache')
+ ->willReturn($cache);
+
+ $this->arrayCache->expects($this->once())->method('set')
+ ->with('encryption_copy_version_' . $sourceInternalPath, true);
+
+ if ($copyResult) {
+ $cache->expects($this->once())->method('get')
+ ->with($sourceInternalPath)
+ ->willReturn(new CacheEntry(['encrypted' => $encrypted, 'size' => 42]));
+ if ($encrypted) {
+ $instance->expects($this->once())->method('updateUnencryptedSize')
+ ->with($mountPoint . $targetInternalPath, 42);
+ } else {
+ $instance->expects($this->never())->method('updateUnencryptedSize');
+ }
+ } else {
+ $instance->expects($this->never())->method('updateUnencryptedSize');
+ }
+
+ $result = $this->invokePrivate(
+ $instance,
+ 'copyBetweenStorage',
+ [
+ $sourceStorage,
+ $sourceInternalPath,
+ $targetInternalPath,
+ false,
+ false
+ ]
+ );
+
+ $this->assertSame($copyResult, $result);
+ }
+
+ public static function dataTestCopyBetweenStorageVersions(): array {
+ return [
+ ['/files/foo.txt', '/files_versions/foo.txt.768743', true, true],
+ ['/files/foo.txt', '/files_versions/foo.txt.768743', true, false],
+ ['/files/foo.txt', '/files_versions/foo.txt.768743', false, true],
+ ['/files/foo.txt', '/files_versions/foo.txt.768743', false, false],
+ ['/files_versions/foo.txt.6487634', '/files/foo.txt', true, true],
+ ['/files_versions/foo.txt.6487634', '/files/foo.txt', true, false],
+ ['/files_versions/foo.txt.6487634', '/files/foo.txt', false, true],
+ ['/files_versions/foo.txt.6487634', '/files/foo.txt', false, false],
+
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param bool $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestIsVersion')]
+ public function testIsVersion($path, $expected): void {
+ $this->assertSame($expected,
+ $this->invokePrivate($this->instance, 'isVersion', [$path])
+ );
+ }
+
+ public static function dataTestIsVersion(): array {
+ return [
+ ['files_versions/foo', true],
+ ['/files_versions/foo', true],
+ ['//files_versions/foo', true],
+ ['files/versions/foo', false],
+ ['files/files_versions/foo', false],
+ ['files_versions_test/foo', false],
+ ];
+ }
+
+ /**
+ *
+ * @param bool $encryptMountPoint
+ * @param mixed $encryptionModule
+ * @param bool $encryptionModuleShouldEncrypt
+ * @param bool $expected
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataTestShouldEncrypt')]
+ public function testShouldEncrypt(
+ $encryptMountPoint,
+ $encryptionModule,
+ $encryptionModuleShouldEncrypt,
+ $expected,
+ ): void {
+ $encryptionManager = $this->createMock(\OC\Encryption\Manager::class);
+ $util = $this->createMock(Util::class);
+ $fileHelper = $this->createMock(IFile::class);
+ $keyStorage = $this->createMock(IStorage::class);
+ $mountManager = $this->createMock(\OC\Files\Mount\Manager::class);
+ $mount = $this->createMock(IMountPoint::class);
+ $arrayCache = $this->createMock(ArrayCache::class);
+ $path = '/welcome.txt';
+ $fullPath = 'admin/files/welcome.txt';
+ $defaultEncryptionModule = $this->createMock(IEncryptionModule::class);
+
+ $wrapper = $this->getMockBuilder(Encryption::class)
+ ->setConstructorArgs(
+ [
+ ['mountPoint' => '', 'mount' => $mount, 'storage' => ''],
+ $encryptionManager,
+ $util,
+ $this->logger,
+ $fileHelper,
+ null,
+ $keyStorage,
+ $mountManager,
+ $arrayCache
+ ]
+ )
+ ->onlyMethods(['getFullPath', 'getEncryptionModule'])
+ ->getMock();
+
+ if ($encryptionModule === true) {
+ /** @var IEncryptionModule|MockObject $encryptionModule */
+ $encryptionModule = $this->createMock(IEncryptionModule::class);
+ }
+
+ $wrapper->method('getFullPath')->with($path)->willReturn($fullPath);
+ $wrapper->expects($encryptMountPoint ? $this->once() : $this->never())
+ ->method('getEncryptionModule')
+ ->with($fullPath)
+ ->willReturnCallback(
+ function () use ($encryptionModule) {
+ if ($encryptionModule === false) {
+ throw new ModuleDoesNotExistsException();
+ }
+ return $encryptionModule;
+ }
+ );
+ $mount->expects($this->once())->method('getOption')->with('encrypt', true)
+ ->willReturn($encryptMountPoint);
+
+ if ($encryptionModule !== null && $encryptionModule !== false) {
+ $encryptionModule
+ ->method('shouldEncrypt')
+ ->with($fullPath)
+ ->willReturn($encryptionModuleShouldEncrypt);
+ }
+
+ if ($encryptionModule === null) {
+ $encryptionManager->expects($this->once())
+ ->method('getEncryptionModule')
+ ->willReturn($defaultEncryptionModule);
+ }
+ $defaultEncryptionModule->method('shouldEncrypt')->willReturn(true);
+
+ $result = $this->invokePrivate($wrapper, 'shouldEncrypt', [$path]);
+
+ $this->assertSame($expected, $result);
+ }
+
+ public static function dataTestShouldEncrypt(): array {
+ return [
+ [false, false, false, false],
+ [true, false, false, false],
+ [true, true, false, false],
+ [true, true, true, true],
+ [true, null, false, true],
+ ];
+ }
+}
diff --git a/tests/lib/Files/Storage/Wrapper/JailTest.php b/tests/lib/Files/Storage/Wrapper/JailTest.php
new file mode 100644
index 00000000000..0043e37ba33
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/JailTest.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Storage\Wrapper;
+
+use OC\Files\Filesystem;
+use OC\Files\Storage\Temporary;
+use OC\Files\Storage\Wrapper\Jail;
+
+class JailTest extends \Test\Files\Storage\Storage {
+ /**
+ * @var Temporary
+ */
+ private $sourceStorage;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->sourceStorage = new Temporary([]);
+ $this->sourceStorage->mkdir('foo');
+ $this->instance = new Jail([
+ 'storage' => $this->sourceStorage,
+ 'root' => 'foo'
+ ]);
+ }
+
+ protected function tearDown(): void {
+ // test that nothing outside our jail is touched
+ $contents = [];
+ $dh = $this->sourceStorage->opendir('');
+ while (($file = readdir($dh)) !== false) {
+ if (!Filesystem::isIgnoredDir($file)) {
+ $contents[] = $file;
+ }
+ }
+ $this->assertEquals(['foo'], $contents);
+ $this->sourceStorage->cleanUp();
+ parent::tearDown();
+ }
+
+ public function testMkDirRooted(): void {
+ $this->instance->mkdir('bar');
+ $this->assertTrue($this->sourceStorage->is_dir('foo/bar'));
+ }
+
+ public function testFilePutContentsRooted(): void {
+ $this->instance->file_put_contents('bar', 'asd');
+ $this->assertEquals('asd', $this->sourceStorage->file_get_contents('foo/bar'));
+ }
+}
diff --git a/tests/lib/Files/Storage/Wrapper/KnownMtimeTest.php b/tests/lib/Files/Storage/Wrapper/KnownMtimeTest.php
new file mode 100644
index 00000000000..b1b5582b4ed
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/KnownMtimeTest.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace lib\Files\Storage\Wrapper;
+
+use OC\Files\Storage\Temporary;
+use OC\Files\Storage\Wrapper\KnownMtime;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Clock\ClockInterface;
+use Test\Files\Storage\Storage;
+
+/**
+ * @group DB
+ */
+class KnownMtimeTest extends Storage {
+ /** @var Temporary */
+ private $sourceStorage;
+
+ /** @var ClockInterface|MockObject */
+ private $clock;
+ private int $fakeTime = 0;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->fakeTime = 0;
+ $this->sourceStorage = new Temporary([]);
+ $this->clock = $this->createMock(ClockInterface::class);
+ $this->clock->method('now')->willReturnCallback(function () {
+ if ($this->fakeTime) {
+ return new \DateTimeImmutable("@{$this->fakeTime}");
+ } else {
+ return new \DateTimeImmutable();
+ }
+ });
+ $this->instance = $this->getWrappedStorage();
+ }
+
+ protected function tearDown(): void {
+ $this->sourceStorage->cleanUp();
+ parent::tearDown();
+ }
+
+ protected function getWrappedStorage() {
+ return new KnownMtime([
+ 'storage' => $this->sourceStorage,
+ 'clock' => $this->clock,
+ ]);
+ }
+
+ public function testNewerKnownMtime(): void {
+ $future = time() + 1000;
+ $this->fakeTime = $future;
+
+ $this->instance->file_put_contents('foo.txt', 'bar');
+
+ // fuzzy match since the clock might have ticked
+ $this->assertLessThan(2, abs(time() - $this->sourceStorage->filemtime('foo.txt')));
+ $this->assertEquals($this->sourceStorage->filemtime('foo.txt'), $this->sourceStorage->stat('foo.txt')['mtime']);
+ $this->assertEquals($this->sourceStorage->filemtime('foo.txt'), $this->sourceStorage->getMetaData('foo.txt')['mtime']);
+
+ $this->assertEquals($future, $this->instance->filemtime('foo.txt'));
+ $this->assertEquals($future, $this->instance->stat('foo.txt')['mtime']);
+ $this->assertEquals($future, $this->instance->getMetaData('foo.txt')['mtime']);
+ }
+}
diff --git a/tests/lib/Files/Storage/Wrapper/PermissionsMaskTest.php b/tests/lib/Files/Storage/Wrapper/PermissionsMaskTest.php
new file mode 100644
index 00000000000..a2f3460c58c
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/PermissionsMaskTest.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Storage\Wrapper;
+
+use OC\Files\Storage\Temporary;
+use OC\Files\Storage\Wrapper\PermissionsMask;
+use OC\Files\Storage\Wrapper\Wrapper;
+use OCP\Constants;
+use OCP\Files\Cache\IScanner;
+
+/**
+ * @group DB
+ */
+class PermissionsMaskTest extends \Test\Files\Storage\Storage {
+ /**
+ * @var Temporary
+ */
+ private $sourceStorage;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->sourceStorage = new Temporary([]);
+ $this->instance = $this->getMaskedStorage(Constants::PERMISSION_ALL);
+ }
+
+ protected function tearDown(): void {
+ $this->sourceStorage->cleanUp();
+ parent::tearDown();
+ }
+
+ protected function getMaskedStorage($mask) {
+ return new PermissionsMask([
+ 'storage' => $this->sourceStorage,
+ 'mask' => $mask
+ ]);
+ }
+
+ public function testMkdirNoCreate(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE);
+ $this->assertFalse($storage->mkdir('foo'));
+ $this->assertFalse($storage->file_exists('foo'));
+ }
+
+ public function testRmdirNoDelete(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE);
+ $this->assertTrue($storage->mkdir('foo'));
+ $this->assertTrue($storage->file_exists('foo'));
+ $this->assertFalse($storage->rmdir('foo'));
+ $this->assertTrue($storage->file_exists('foo'));
+ }
+
+ public function testTouchNewFileNoCreate(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE);
+ $this->assertFalse($storage->touch('foo'));
+ $this->assertFalse($storage->file_exists('foo'));
+ }
+
+ public function testTouchNewFileNoUpdate(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE);
+ $this->assertTrue($storage->touch('foo'));
+ $this->assertTrue($storage->file_exists('foo'));
+ }
+
+ public function testTouchExistingFileNoUpdate(): void {
+ $this->sourceStorage->touch('foo');
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE);
+ $this->assertFalse($storage->touch('foo'));
+ }
+
+ public function testUnlinkNoDelete(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE);
+ $this->assertTrue($storage->touch('foo'));
+ $this->assertTrue($storage->file_exists('foo'));
+ $this->assertFalse($storage->unlink('foo'));
+ $this->assertTrue($storage->file_exists('foo'));
+ }
+
+ public function testPutContentsNewFileNoUpdate(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE);
+ $this->assertEquals(3, $storage->file_put_contents('foo', 'bar'));
+ $this->assertEquals('bar', $storage->file_get_contents('foo'));
+ }
+
+ public function testPutContentsNewFileNoCreate(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE);
+ $this->assertFalse($storage->file_put_contents('foo', 'bar'));
+ }
+
+ public function testPutContentsExistingFileNoUpdate(): void {
+ $this->sourceStorage->touch('foo');
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE);
+ $this->assertFalse($storage->file_put_contents('foo', 'bar'));
+ }
+
+ public function testFopenExistingFileNoUpdate(): void {
+ $this->sourceStorage->touch('foo');
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE);
+ $this->assertFalse($storage->fopen('foo', 'w'));
+ }
+
+ public function testFopenNewFileNoCreate(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE);
+ $this->assertFalse($storage->fopen('foo', 'w'));
+ }
+
+ public function testScanNewFiles(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_READ + Constants::PERMISSION_CREATE);
+ $storage->file_put_contents('foo', 'bar');
+ $storage->getScanner()->scan('');
+
+ $this->assertEquals(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE, $this->sourceStorage->getCache()->get('foo')->getPermissions());
+ $this->assertEquals(Constants::PERMISSION_READ, $storage->getCache()->get('foo')->getPermissions());
+ }
+
+ public function testScanNewWrappedFiles(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_READ + Constants::PERMISSION_CREATE);
+ $wrappedStorage = new Wrapper(['storage' => $storage]);
+ $wrappedStorage->file_put_contents('foo', 'bar');
+ $wrappedStorage->getScanner()->scan('');
+
+ $this->assertEquals(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE, $this->sourceStorage->getCache()->get('foo')->getPermissions());
+ $this->assertEquals(Constants::PERMISSION_READ, $storage->getCache()->get('foo')->getPermissions());
+ }
+
+ public function testScanNewFilesNested(): void {
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_READ + Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE);
+ $nestedStorage = new PermissionsMask([
+ 'storage' => $storage,
+ 'mask' => Constants::PERMISSION_READ + Constants::PERMISSION_CREATE
+ ]);
+ $wrappedStorage = new Wrapper(['storage' => $nestedStorage]);
+ $wrappedStorage->file_put_contents('foo', 'bar');
+ $wrappedStorage->getScanner()->scan('');
+
+ $this->assertEquals(Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE, $this->sourceStorage->getCache()->get('foo')->getPermissions());
+ $this->assertEquals(Constants::PERMISSION_READ + Constants::PERMISSION_UPDATE, $storage->getCache()->get('foo')->getPermissions());
+ $this->assertEquals(Constants::PERMISSION_READ, $wrappedStorage->getCache()->get('foo')->getPermissions());
+ }
+
+ public function testScanUnchanged(): void {
+ $this->sourceStorage->mkdir('foo');
+ $this->sourceStorage->file_put_contents('foo/bar.txt', 'bar');
+
+ $this->sourceStorage->getScanner()->scan('foo');
+
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_READ);
+ $scanner = $storage->getScanner();
+ $called = false;
+ $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function () use (&$called): void {
+ $called = true;
+ });
+ $scanner->scan('foo', IScanner::SCAN_RECURSIVE, IScanner::REUSE_ETAG | IScanner::REUSE_SIZE);
+
+ $this->assertFalse($called);
+ }
+
+ public function testScanUnchangedWrapped(): void {
+ $this->sourceStorage->mkdir('foo');
+ $this->sourceStorage->file_put_contents('foo/bar.txt', 'bar');
+
+ $this->sourceStorage->getScanner()->scan('foo');
+
+ $storage = $this->getMaskedStorage(Constants::PERMISSION_READ);
+ $wrappedStorage = new Wrapper(['storage' => $storage]);
+ $scanner = $wrappedStorage->getScanner();
+ $called = false;
+ $scanner->listen('\OC\Files\Cache\Scanner', 'addToCache', function () use (&$called): void {
+ $called = true;
+ });
+ $scanner->scan('foo', IScanner::SCAN_RECURSIVE, IScanner::REUSE_ETAG | IScanner::REUSE_SIZE);
+
+ $this->assertFalse($called);
+ }
+}
diff --git a/tests/lib/Files/Storage/Wrapper/QuotaTest.php b/tests/lib/Files/Storage/Wrapper/QuotaTest.php
new file mode 100644
index 00000000000..2878fe6ca92
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/QuotaTest.php
@@ -0,0 +1,232 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Storage\Wrapper;
+
+//ensure the constants are loaded
+use OC\Files\Cache\CacheEntry;
+use OC\Files\Storage\Local;
+use OC\Files\Storage\Wrapper\Quota;
+use OCP\Files;
+use OCP\ITempManager;
+use OCP\Server;
+
+/**
+ * Class QuotaTest
+ *
+ * @group DB
+ *
+ * @package Test\Files\Storage\Wrapper
+ */
+class QuotaTest extends \Test\Files\Storage\Storage {
+ /**
+ * @var string tmpDir
+ */
+ private $tmpDir;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->tmpDir = Server::get(ITempManager::class)->getTemporaryFolder();
+ $storage = new Local(['datadir' => $this->tmpDir]);
+ $this->instance = new Quota(['storage' => $storage, 'quota' => 10000000]);
+ }
+
+ protected function tearDown(): void {
+ Files::rmdirr($this->tmpDir);
+ parent::tearDown();
+ }
+
+ /**
+ * @param integer $limit
+ */
+ protected function getLimitedStorage($limit) {
+ $storage = new Local(['datadir' => $this->tmpDir]);
+ $storage->mkdir('files');
+ $storage->getScanner()->scan('');
+ return new Quota(['storage' => $storage, 'quota' => $limit]);
+ }
+
+ public function testFilePutContentsNotEnoughSpace(): void {
+ $instance = $this->getLimitedStorage(3);
+ $this->assertFalse($instance->file_put_contents('files/foo', 'foobar'));
+ }
+
+ public function testCopyNotEnoughSpace(): void {
+ $instance = $this->getLimitedStorage(9);
+ $this->assertEquals(6, $instance->file_put_contents('files/foo', 'foobar'));
+ $instance->getScanner()->scan('');
+ $this->assertFalse($instance->copy('files/foo', 'files/bar'));
+ }
+
+ public function testFreeSpace(): void {
+ $instance = $this->getLimitedStorage(9);
+ $this->assertEquals(9, $instance->free_space(''));
+ }
+
+ public function testFreeSpaceWithUsedSpace(): void {
+ $instance = $this->getLimitedStorage(9);
+ $instance->getCache()->put(
+ '', ['size' => 3]
+ );
+ $this->assertEquals(6, $instance->free_space(''));
+ }
+
+ public function testFreeSpaceWithUnknownDiskSpace(): void {
+ $storage = $this->getMockBuilder(Local::class)
+ ->onlyMethods(['free_space'])
+ ->setConstructorArgs([['datadir' => $this->tmpDir]])
+ ->getMock();
+ $storage->expects($this->any())
+ ->method('free_space')
+ ->willReturn(-2);
+ $storage->getScanner()->scan('');
+
+ $instance = new Quota(['storage' => $storage, 'quota' => 9]);
+ $instance->getCache()->put(
+ '', ['size' => 3]
+ );
+ $this->assertEquals(6, $instance->free_space(''));
+ }
+
+ public function testFreeSpaceWithUsedSpaceAndEncryption(): void {
+ $instance = $this->getLimitedStorage(9);
+ $instance->getCache()->put(
+ '', ['size' => 7]
+ );
+ $this->assertEquals(2, $instance->free_space(''));
+ }
+
+ public function testFWriteNotEnoughSpace(): void {
+ $instance = $this->getLimitedStorage(9);
+ $stream = $instance->fopen('files/foo', 'w+');
+ $this->assertEquals(6, fwrite($stream, 'foobar'));
+ $this->assertEquals(3, fwrite($stream, 'qwerty'));
+ fclose($stream);
+ $this->assertEquals('foobarqwe', $instance->file_get_contents('files/foo'));
+ }
+
+ public function testStreamCopyWithEnoughSpace(): void {
+ $instance = $this->getLimitedStorage(16);
+ $inputStream = fopen('data://text/plain,foobarqwerty', 'r');
+ $outputStream = $instance->fopen('files/foo', 'w+');
+ [$count, $result] = \OC_Helper::streamCopy($inputStream, $outputStream);
+ $this->assertEquals(12, $count);
+ $this->assertTrue($result);
+ fclose($inputStream);
+ fclose($outputStream);
+ }
+
+ public function testStreamCopyNotEnoughSpace(): void {
+ $instance = $this->getLimitedStorage(9);
+ $inputStream = fopen('data://text/plain,foobarqwerty', 'r');
+ $outputStream = $instance->fopen('files/foo', 'w+');
+ [$count, $result] = \OC_Helper::streamCopy($inputStream, $outputStream);
+ $this->assertEquals(9, $count);
+ $this->assertFalse($result);
+ fclose($inputStream);
+ fclose($outputStream);
+ }
+
+ public function testReturnFalseWhenFopenFailed(): void {
+ $failStorage = $this->getMockBuilder(Local::class)
+ ->onlyMethods(['fopen'])
+ ->setConstructorArgs([['datadir' => $this->tmpDir]])
+ ->getMock();
+ $failStorage->expects($this->any())
+ ->method('fopen')
+ ->willReturn(false);
+
+ $instance = new Quota(['storage' => $failStorage, 'quota' => 1000]);
+
+ $this->assertFalse($instance->fopen('failedfopen', 'r'));
+ }
+
+ public function testReturnRegularStreamOnRead(): void {
+ $instance = $this->getLimitedStorage(9);
+
+ // create test file first
+ $stream = $instance->fopen('files/foo', 'w+');
+ fwrite($stream, 'blablacontent');
+ fclose($stream);
+
+ $stream = $instance->fopen('files/foo', 'r');
+ $meta = stream_get_meta_data($stream);
+ $this->assertEquals('plainfile', $meta['wrapper_type']);
+ fclose($stream);
+
+ $stream = $instance->fopen('files/foo', 'rb');
+ $meta = stream_get_meta_data($stream);
+ $this->assertEquals('plainfile', $meta['wrapper_type']);
+ fclose($stream);
+ }
+
+ public function testReturnRegularStreamWhenOutsideFiles(): void {
+ $instance = $this->getLimitedStorage(9);
+ $instance->mkdir('files_other');
+
+ // create test file first
+ $stream = $instance->fopen('files_other/foo', 'w+');
+ $meta = stream_get_meta_data($stream);
+ $this->assertEquals('plainfile', $meta['wrapper_type']);
+ fclose($stream);
+ }
+
+ public function testReturnQuotaStreamOnWrite(): void {
+ $instance = $this->getLimitedStorage(9);
+ $stream = $instance->fopen('files/foo', 'w+');
+ $meta = stream_get_meta_data($stream);
+ $expected_type = 'user-space';
+ $this->assertEquals($expected_type, $meta['wrapper_type']);
+ fclose($stream);
+ }
+
+ public function testSpaceRoot(): void {
+ $storage = $this->getMockBuilder(Local::class)->disableOriginalConstructor()->getMock();
+ $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')->disableOriginalConstructor()->getMock();
+ $storage->expects($this->once())
+ ->method('getCache')
+ ->willReturn($cache);
+ $storage->expects($this->once())
+ ->method('free_space')
+ ->willReturn(2048);
+ $cache->expects($this->once())
+ ->method('get')
+ ->with('files')
+ ->willReturn(new CacheEntry(['size' => 50]));
+
+ $instance = new Quota(['storage' => $storage, 'quota' => 1024, 'root' => 'files']);
+
+ $this->assertEquals(1024 - 50, $instance->free_space(''));
+ }
+
+ public function testInstanceOfStorageWrapper(): void {
+ $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Local'));
+ $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Wrapper'));
+ $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota'));
+ }
+
+ public function testNoMkdirQuotaZero(): void {
+ $instance = $this->getLimitedStorage(0.0);
+ $this->assertFalse($instance->mkdir('files'));
+ $this->assertFalse($instance->mkdir('files/foobar'));
+ }
+
+ public function testMkdirQuotaZeroTrashbin(): void {
+ $instance = $this->getLimitedStorage(0.0);
+ $this->assertTrue($instance->mkdir('files_trashbin'));
+ $this->assertTrue($instance->mkdir('files_trashbin/files'));
+ $this->assertTrue($instance->mkdir('files_versions'));
+ $this->assertTrue($instance->mkdir('cache'));
+ }
+
+ public function testNoTouchQuotaZero(): void {
+ $instance = $this->getLimitedStorage(0.0);
+ $this->assertFalse($instance->touch('foobar'));
+ }
+}
diff --git a/tests/lib/Files/Storage/Wrapper/WrapperTest.php b/tests/lib/Files/Storage/Wrapper/WrapperTest.php
new file mode 100644
index 00000000000..60f139450c7
--- /dev/null
+++ b/tests/lib/Files/Storage/Wrapper/WrapperTest.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Storage\Wrapper;
+
+use OC\Files\Storage\Local;
+use OC\Files\Storage\Wrapper\Wrapper;
+use OCP\Files;
+use OCP\ITempManager;
+use OCP\Server;
+
+class WrapperTest extends \Test\Files\Storage\Storage {
+ /**
+ * @var string tmpDir
+ */
+ private $tmpDir;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->tmpDir = Server::get(ITempManager::class)->getTemporaryFolder();
+ $storage = new Local(['datadir' => $this->tmpDir]);
+ $this->instance = new Wrapper(['storage' => $storage]);
+ }
+
+ protected function tearDown(): void {
+ Files::rmdirr($this->tmpDir);
+ parent::tearDown();
+ }
+
+ public function testInstanceOfStorageWrapper(): void {
+ $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Local'));
+ $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Wrapper'));
+ }
+}