diff --git a/tests/lib/Files/Storage/Wrapper/QuotaTest.php b/tests/lib/Files/Storage/Wrapper/QuotaTest.php index 150917b9dd9e9..086dc0ee12826 100644 --- a/tests/lib/Files/Storage/Wrapper/QuotaTest.php +++ b/tests/lib/Files/Storage/Wrapper/QuotaTest.php @@ -13,6 +13,7 @@ use OC\Files\Storage\Local; use OC\Files\Storage\Wrapper\Quota; use OCP\Files; +use OCP\Files\FileInfo; use OCP\Files\NotEnoughSpaceException; use OCP\ITempManager; use OCP\Server; @@ -60,6 +61,28 @@ public function testFilePutContentsNotEnoughSpace(): void { $this->assertFalse($instance->file_put_contents('files/foo', 'foobar')); } + public function testFilePutContentsRejectsExactQuotaLimit(): void { + $instance = $this->getLimitedStorage(3); + + // Quota currently uses a strict "<" comparison, so an exact fit is + // rejected rather than accepted. + $this->assertFalse($instance->file_put_contents('files/foo', 'foo')); + } + + public function testSizedWriteStreamRejectsExactQuotaLimit(): void { + $instance = $this->getLimitedStorage(3); + $stream = fopen('php://temp', 'w+'); + fwrite($stream, 'foo'); + rewind($stream); + + $this->expectException(NotEnoughSpaceException::class); + try { + $instance->writeStream('files/test.txt', $stream, 3); + } finally { + fclose($stream); + } + } + public function testCopyNotEnoughSpace(): void { $instance = $this->getLimitedStorage(9); $this->assertEquals(6, $instance->file_put_contents('files/foo', 'foobar')); @@ -97,6 +120,51 @@ public function testFreeSpaceWithUnknownDiskSpace(): void { $this->assertEquals(6, $instance->free_space('')); } + public function testQuotaCallbackIsUsedAndCached(): void { + $storage = new Local(['datadir' => $this->tmpDir]); + $storage->mkdir('files'); + $storage->getScanner()->scan(''); + + $callbackCalls = 0; + $instance = new Quota([ + 'storage' => $storage, + 'quotaCallback' => static function () use (&$callbackCalls): int { + $callbackCalls++; + return 9; + }, + ]); + + $this->assertSame(9, $instance->getQuota()); + $this->assertSame(9, $instance->getQuota()); + $this->assertSame(1, $callbackCalls); + } + + public function testUnlimitedQuotaDelegatesToWrappedStorage(): void { + $instance = $this->getLimitedStorage(FileInfo::SPACE_UNLIMITED); + + $this->assertSame( + 100, + $instance->file_put_contents('files/foo', str_repeat('x', 100)) + ); + } + + public function testNegativeQuotaDelegatesToWrappedStorage(): void { + $instance = $this->getLimitedStorage(-1); + + $this->assertSame( + 100, + $instance->file_put_contents('files/foo', str_repeat('x', 100)) + ); + } + + public function testQuotaCanBeDisabled(): void { + $instance = $this->getLimitedStorage(0); + $instance->enableQuota(false); + + $this->assertSame(3, $instance->file_put_contents('files/foo', 'foo')); + $this->assertSame('foo', $instance->file_get_contents('files/foo')); + } + public function testFreeSpaceWithUsedSpaceAndEncryption(): void { $instance = $this->getLimitedStorage(9); $instance->getCache()->put( @@ -214,7 +282,6 @@ public function testInstanceOfStorageWrapper(): void { public function testNoMkdirQuotaZero(): void { $instance = $this->getLimitedStorage(0.0); - $this->assertFalse($instance->mkdir('files')); $this->assertFalse($instance->mkdir('files/foobar')); } @@ -226,6 +293,29 @@ public function testMkdirQuotaZeroTrashbin(): void { $this->assertTrue($instance->mkdir('cache')); } + public function testCacheAndUploadsBypassQuota(): void { + $instance = $this->getLimitedStorage(0.0); + + $this->assertTrue($instance->mkdir('uploads')); + $this->assertSame(3, $instance->file_put_contents('uploads/foo', 'foo')); + $this->assertSame('foo', $instance->file_get_contents('uploads/foo')); + + $this->assertTrue($instance->mkdir('cache')); + $this->assertSame(3, $instance->file_put_contents('cache/foo', 'foo')); + $this->assertSame('foo', $instance->file_get_contents('cache/foo')); + } + + public function testPartFilesBypassQuotaWhenOpenedForWriting(): void { + $instance = $this->getLimitedStorage(0.0); + $stream = $instance->fopen('files/foo.part', 'w'); + + $this->assertIsResource($stream); + $this->assertSame(3, fwrite($stream, 'foo')); + fclose($stream); + + $this->assertSame('foo', $instance->file_get_contents('files/foo.part')); + } + public function testNoTouchQuotaZero(): void { $instance = $this->getLimitedStorage(0.0); $this->assertFalse($instance->touch('foobar')); @@ -242,13 +332,89 @@ public function testNoWriteStreamQuota(): void { $stream = fopen('php://temp', 'w+'); fwrite($stream, 'foo'); rewind($stream); - $instance->writeStream('files/test.txt', $stream); + $this->assertSame(3, $instance->writeStream('files/test.txt', $stream)); + + $this->assertSame('foo', $instance->file_get_contents('files/test.txt')); $stream = fopen('php://temp', 'w+'); fwrite($stream, 'foobar'); rewind($stream); $this->expectException(NotEnoughSpaceException::class); $instance->writeStream('files/test.txt', $stream); + fclose($stream); + } + + public function testSizedWriteStreamWithEnoughSpace(): void { + $instance = $this->getLimitedStorage(5); + $stream = fopen('php://temp', 'w+'); + fwrite($stream, 'foo'); + rewind($stream); + + $this->assertSame( + 3, + $instance->writeStream('files/test.txt', $stream, 3) + ); + + $this->assertSame('foo', $instance->file_get_contents('files/test.txt')); + } + + public function testSizedWriteStreamRejectsWhenSizeReachesFreeSpace(): void { + $instance = $this->getLimitedStorage(3); + $stream = fopen('php://temp', 'w+'); + fwrite($stream, 'foo'); + rewind($stream); + + try { + $this->expectException(NotEnoughSpaceException::class); + $instance->writeStream('files/test.txt', $stream, 3); + } finally { + fclose($stream); + } + } + + public function testCopyFromStorageHonorsQuota(): void { + $instance = $this->getLimitedStorage(3); + $sourceDir = Server::get(ITempManager::class)->getTemporaryFolder(); + + try { + $sourceStorage = new Local(['datadir' => $sourceDir]); + $sourceStorage->file_put_contents('source.txt', 'foo'); + $sourceStorage->getScanner()->scan(''); + + $this->assertFalse( + $instance->copyFromStorage( + $sourceStorage, + 'source.txt', + 'files/target.txt' + ) + ); + $this->assertFalse($instance->file_exists('files/target.txt')); + } finally { + Files::rmdirr($sourceDir); + } + } + + public function testMoveFromStorageHonorsQuota(): void { + $instance = $this->getLimitedStorage(3); + $sourceDir = Server::get(ITempManager::class)->getTemporaryFolder(); + + try { + $sourceStorage = new Local(['datadir' => $sourceDir]); + $sourceStorage->file_put_contents('source.txt', 'foo'); + $sourceStorage->getScanner()->scan(''); + + $this->assertFalse( + $instance->moveFromStorage( + $sourceStorage, + 'source.txt', + 'files/target.txt' + ) + ); + $this->assertTrue($sourceStorage->file_exists('source.txt')); + $this->assertFalse($instance->file_exists('files/target.txt')); + } finally { + Files::rmdirr($sourceDir); + } } public function testNoWriteStreamQuotaZero(): void { diff --git a/tests/lib/HelperStorageTest.php b/tests/lib/HelperStorageTest.php index a124b47cec26f..123087506db7f 100644 --- a/tests/lib/HelperStorageTest.php +++ b/tests/lib/HelperStorageTest.php @@ -162,6 +162,29 @@ public function testGetStorageInfoIncludingExtStorage(): void { $config->setUserValue($this->user, 'files', 'quota', 'default'); } + /** + * Test that the quota wrapper includes mounted storage when configured. + */ + public function testQuotaIncludesExternalStorage(): void { + $homeStorage = new Temporary([]); + $homeStorage->file_put_contents('test.txt', '01234'); + $homeStorage->getScanner()->scan(''); + + $quotaStorage = new Quota([ + 'storage' => $homeStorage, + 'quota' => 25, + 'include_external_storage' => true, + ]); + Filesystem::mount($quotaStorage, [], '/' . $this->user . '/files'); + + $externalStorage = new Temporary([]); + $externalStorage->file_put_contents('extfile.txt', 'abcdefghijklmnopq'); + $externalStorage->getScanner()->scan(''); + Filesystem::mount($externalStorage, [], '/' . $this->user . '/files/ext'); + + $this->assertSame(3, $quotaStorage->free_space('')); + } + /** * Test getting the storage info excluding extra mount points * when user has no quota set, even when quota ext storage option