From 59a118d11e6e0d3da47a2efc2f40981fe65b4194 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 7 Aug 2026 10:24:13 -0400 Subject: [PATCH 1/4] test(files): strengthen quota wrapper coverage Remove the invalid existing-directory mkdir assertion, cover exact quota boundaries for writes, and verify successful writeStream results and cleanup. Assisted-by: Copilot:gpt-5.6-luna Signed-off-by: Josh --- tests/lib/Files/Storage/Wrapper/QuotaTest.php | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/lib/Files/Storage/Wrapper/QuotaTest.php b/tests/lib/Files/Storage/Wrapper/QuotaTest.php index 150917b9dd9e9..0d92194cb949f 100644 --- a/tests/lib/Files/Storage/Wrapper/QuotaTest.php +++ b/tests/lib/Files/Storage/Wrapper/QuotaTest.php @@ -60,6 +60,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')); @@ -214,7 +236,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')); } @@ -242,13 +263,17 @@ 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)); + fclose($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 testNoWriteStreamQuotaZero(): void { From 251396923b7f7a6f5ff26f0131f93e193096e1b9 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 7 Aug 2026 10:31:52 -0400 Subject: [PATCH 2/4] test(files): expand quota wrapper branch coverage Cover quota callbacks, unlimited and disabled quotas, negative quotas, bypass paths, part files, sized streams, and cross-storage operations. Signed-off-by: Josh --- tests/lib/Files/Storage/Wrapper/QuotaTest.php | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/tests/lib/Files/Storage/Wrapper/QuotaTest.php b/tests/lib/Files/Storage/Wrapper/QuotaTest.php index 0d92194cb949f..bf5d911f7a8e9 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; @@ -119,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( @@ -247,6 +293,28 @@ 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->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')); @@ -276,6 +344,80 @@ public function testNoWriteStreamQuota(): void { 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) + ); + fclose($stream); + + $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 { $instance = $this->getLimitedStorage(0.0); $stream = fopen('php://temp', 'w+'); From 962771e430ab223ba5d9514965088465bd6f0bf0 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 7 Aug 2026 10:39:41 -0400 Subject: [PATCH 3/4] test(files): cover quota accounting for external storage Verify that the quota wrapper includes files from mounted external storage when `include_external_storage` is enabled. Assisted-by: Copilot:gpt-5.6-luna Signed-off-by: Josh --- tests/lib/HelperStorageTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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 From e27c14ce85fb3c6c353c289e89322cd6d712f9c8 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 7 Aug 2026 21:26:21 -0400 Subject: [PATCH 4/4] test(storage): fixup unnecessary fclose calls - writeStream() closes internally - cache/ did not exist Signed-off-by: Josh --- tests/lib/Files/Storage/Wrapper/QuotaTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/lib/Files/Storage/Wrapper/QuotaTest.php b/tests/lib/Files/Storage/Wrapper/QuotaTest.php index bf5d911f7a8e9..086dc0ee12826 100644 --- a/tests/lib/Files/Storage/Wrapper/QuotaTest.php +++ b/tests/lib/Files/Storage/Wrapper/QuotaTest.php @@ -300,6 +300,7 @@ public function testCacheAndUploadsBypassQuota(): void { $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')); } @@ -332,7 +333,6 @@ public function testNoWriteStreamQuota(): void { fwrite($stream, 'foo'); rewind($stream); $this->assertSame(3, $instance->writeStream('files/test.txt', $stream)); - fclose($stream); $this->assertSame('foo', $instance->file_get_contents('files/test.txt')); @@ -354,7 +354,6 @@ public function testSizedWriteStreamWithEnoughSpace(): void { 3, $instance->writeStream('files/test.txt', $stream, 3) ); - fclose($stream); $this->assertSame('foo', $instance->file_get_contents('files/test.txt')); }