From d67c877ac51f762b65de86b8e18b52e5d1a8d32f Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 1 Apr 2025 17:56:05 +0200 Subject: [PATCH] fix(FileAccess): Add tests Signed-off-by: Marcel Klehr --- lib/private/Files/Cache/FileAccess.php | 18 +- tests/lib/Files/Cache/FileAccessTest.php | 438 +++++++++++++++++++++++ 2 files changed, 447 insertions(+), 9 deletions(-) create mode 100644 tests/lib/Files/Cache/FileAccessTest.php diff --git a/lib/private/Files/Cache/FileAccess.php b/lib/private/Files/Cache/FileAccess.php index a57fa42dd93..2ddd18f1601 100644 --- a/lib/private/Files/Cache/FileAccess.php +++ b/lib/private/Files/Cache/FileAccess.php @@ -127,31 +127,31 @@ class FileAccess implements IFileAccess { $path = $root['path'] === '' ? '' : $root['path'] . '/'; - $qb->select('*') - ->from('filecache', 'filecache') - ->andWhere($qb->expr()->like('filecache.path', $qb->createNamedParameter($path . '%'))) - ->andWhere($qb->expr()->eq('filecache.storage', $qb->createNamedParameter($storageId))) - ->andWhere($qb->expr()->gt('filecache.fileid', $qb->createNamedParameter($lastFileId))); + $qb->selectDistinct('*') + ->from('filecache', 'f') + ->andWhere($qb->expr()->like('f.path', $qb->createNamedParameter($path . '%'))) + ->andWhere($qb->expr()->eq('f.storage', $qb->createNamedParameter($storageId))) + ->andWhere($qb->expr()->gt('f.fileid', $qb->createNamedParameter($lastFileId))); if (!$endToEndEncrypted) { // End to end encrypted files are descendants of a folder with encrypted=1 - $qb->innerJoin('filecache', 'filecache', 'p', $qb->expr()->eq('filecache.parent', 'p.fileid')); + $qb->leftJoin('f', 'filecache', 'p', $qb->expr()->eq('f.parent', 'p.fileid')); $qb->andWhere($qb->expr()->eq('p.encrypted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); } if (!$serverSideEncrypted) { // Server side encrypted files have encrypted=1 directly - $qb->andWhere($qb->expr()->eq('filecache.encrypted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + $qb->andWhere($qb->expr()->eq('f.encrypted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); } if (count($mimeTypes) > 0) { - $qb->andWhere($qb->expr()->in('filecache.mimetype', $qb->createNamedParameter($mimeTypes, IQueryBuilder::PARAM_INT_ARRAY))); + $qb->andWhere($qb->expr()->in('f.mimetype', $qb->createNamedParameter($mimeTypes, IQueryBuilder::PARAM_INT_ARRAY))); } if ($maxResults !== 0) { $qb->setMaxResults($maxResults); } - $files = $qb->orderBy('filecache.fileid', 'ASC') + $files = $qb->orderBy('f.fileid', 'ASC') ->executeQuery(); while ( diff --git a/tests/lib/Files/Cache/FileAccessTest.php b/tests/lib/Files/Cache/FileAccessTest.php new file mode 100644 index 00000000000..2078412ad73 --- /dev/null +++ b/tests/lib/Files/Cache/FileAccessTest.php @@ -0,0 +1,438 @@ +dbConnection = \OC::$server->get(IDBConnection::class); + + // Ensure FileAccess is instantiated with the real connection + $this->fileAccess = new FileAccess( + $this->dbConnection, + \OC::$server->get(\OC\SystemConfig::class), + \OC::$server->get(LoggerInterface::class), + \OC::$server->get(\OC\FilesMetadata\FilesMetadataManager::class), + \OC::$server->get(\OCP\Files\IMimeTypeLoader::class) + ); + + // Clear and prepare `filecache` table for tests + $queryBuilder = $this->dbConnection->getQueryBuilder(); + $queryBuilder->delete('filecache')->executeStatement(); + + // Clean up potential leftovers from other tests + $queryBuilder = $this->dbConnection->getQueryBuilder(); + $queryBuilder->delete('mounts')->executeStatement(); + + + $this->setUpTestDatabaseForGetDistinctMounts(); + $this->setUpTestDatabaseForGetByAncestorInStorage(); + } + + private function setUpTestDatabaseForGetDistinctMounts(): void { + $queryBuilder = $this->dbConnection->getQueryBuilder(); + + // Insert test data + $queryBuilder->insert('mounts') + ->values([ + 'storage_id' => $queryBuilder->createNamedParameter(1, IQueryBuilder::PARAM_INT), + 'root_id' => $queryBuilder->createNamedParameter(10, IQueryBuilder::PARAM_INT), + 'mount_provider_class' => $queryBuilder->createNamedParameter('TestProviderClass1'), + 'mount_point' => $queryBuilder->createNamedParameter('/files'), + 'user_id' => $queryBuilder->createNamedParameter('test'), + ]) + ->executeStatement(); + + $queryBuilder->insert('mounts') + ->values([ + 'storage_id' => $queryBuilder->createNamedParameter(2, IQueryBuilder::PARAM_INT), + 'root_id' => $queryBuilder->createNamedParameter(20, IQueryBuilder::PARAM_INT), + 'mount_provider_class' => $queryBuilder->createNamedParameter('TestProviderClass2'), + 'mount_point' => $queryBuilder->createNamedParameter('/cache'), + 'user_id' => $queryBuilder->createNamedParameter('test'), + ]) + ->executeStatement(); + + $queryBuilder->insert('mounts') + ->values([ + 'storage_id' => $queryBuilder->createNamedParameter(3, IQueryBuilder::PARAM_INT), + 'root_id' => $queryBuilder->createNamedParameter(30, IQueryBuilder::PARAM_INT), + 'mount_provider_class' => $queryBuilder->createNamedParameter('TestProviderClass1'), + 'mount_point' => $queryBuilder->createNamedParameter('/trashbin'), + 'user_id' => $queryBuilder->createNamedParameter('test'), + ]) + ->executeStatement(); + } + + /** + * Test that getDistinctMounts returns all mounts without filters + */ + public function testGetDistinctMountsWithoutFilters(): void { + $result = iterator_to_array($this->fileAccess->getDistinctMounts()); + + $this->assertCount(3, $result); + + $this->assertEquals([ + 'storage_id' => 1, + 'root_id' => 10, + 'override_root' => 10, + ], $result[0]); + + $this->assertEquals([ + 'storage_id' => 2, + 'root_id' => 20, + 'override_root' => 20, + ], $result[1]); + + $this->assertEquals([ + 'storage_id' => 3, + 'root_id' => 30, + 'override_root' => 30, + ], $result[2]); + } + + /** + * Test that getDistinctMounts applies filtering by mount providers + */ + public function testGetDistinctMountsWithMountProviderFilter(): void { + $result = iterator_to_array($this->fileAccess->getDistinctMounts(['TestProviderClass1'])); + + $this->assertCount(2, $result); + + $this->assertEquals([ + 'storage_id' => 1, + 'root_id' => 10, + 'override_root' => 10, + ], $result[0]); + + $this->assertEquals([ + 'storage_id' => 3, + 'root_id' => 30, + 'override_root' => 30, + ], $result[1]); + } + + /** + * Test that getDistinctMounts excludes certain mount points + */ + public function testGetDistinctMountsWithExclusionFilter(): void { + $result = iterator_to_array($this->fileAccess->getDistinctMounts([], '/cache')); + + $this->assertCount(2, $result); + + $this->assertEquals([ + 'storage_id' => 1, + 'root_id' => 10, + 'override_root' => 10, + ], $result[0]); + + $this->assertEquals([ + 'storage_id' => 3, + 'root_id' => 30, + 'override_root' => 30, + ], $result[1]); + } + + /** + * Test that getDistinctMounts rewrites home directory paths + */ + public function testGetDistinctMountsWithRewriteHomeDirectories(): void { + // Add additional test data for a home directory mount + $queryBuilder = $this->dbConnection->getQueryBuilder(); + $queryBuilder->insert('mounts') + ->values([ + 'storage_id' => $queryBuilder->createNamedParameter(4, IQueryBuilder::PARAM_INT), + 'root_id' => $queryBuilder->createNamedParameter(40, IQueryBuilder::PARAM_INT), + 'mount_provider_class' => $queryBuilder->createNamedParameter(\OC\Files\Mount\LocalHomeMountProvider::class), + 'mount_point' => $queryBuilder->createNamedParameter('/home/user'), + 'user_id' => $queryBuilder->createNamedParameter('test'), + ]) + ->executeStatement(); + + // Simulate adding a "files" directory to the filecache table + $queryBuilder->delete('filecache')->executeStatement(); + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => $queryBuilder->createNamedParameter(99, IQueryBuilder::PARAM_INT), + 'storage' => $queryBuilder->createNamedParameter(4, IQueryBuilder::PARAM_INT), + 'path' => $queryBuilder->createNamedParameter('files'), + ]) + ->executeStatement(); + + $result = iterator_to_array($this->fileAccess->getDistinctMounts()); + + $this->assertEquals([ + 'storage_id' => 4, + 'root_id' => 40, + 'override_root' => 99, + ], end($result)); + } + + private function setUpTestDatabaseForGetByAncestorInStorage(): void { + // prepare `filecache` table for tests + $queryBuilder = $this->dbConnection->getQueryBuilder(); + + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => 1, + 'parent' => 0, + 'path' => $queryBuilder->createNamedParameter('files'), + 'path_hash' => $queryBuilder->createNamedParameter(md5('files')), + 'storage' => $queryBuilder->createNamedParameter(1), + 'name' => $queryBuilder->createNamedParameter('files'), + 'mimetype' => 1, + 'encrypted' => 0, + ]) + ->executeStatement(); + + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => 2, + 'parent' => 1, + 'path' => $queryBuilder->createNamedParameter('files/documents'), + 'path_hash' => $queryBuilder->createNamedParameter(md5('files/documents')), + 'storage' => $queryBuilder->createNamedParameter(1), + 'name' => $queryBuilder->createNamedParameter('documents'), + 'mimetype' => 2, + 'encrypted' => 1, + ]) + ->executeStatement(); + + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => 3, + 'parent' => 1, + 'path' => $queryBuilder->createNamedParameter('files/photos'), + 'path_hash' => $queryBuilder->createNamedParameter(md5('files/photos')), + 'storage' => $queryBuilder->createNamedParameter(1), + 'name' => $queryBuilder->createNamedParameter('photos'), + 'mimetype' => 3, + 'encrypted' => 1, + ]) + ->executeStatement(); + + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => 4, + 'parent' => 3, + 'path' => $queryBuilder->createNamedParameter('files/photos/endtoendencrypted'), + 'path_hash' => $queryBuilder->createNamedParameter(md5('files/photos/endtoendencrypted')), + 'storage' => $queryBuilder->createNamedParameter(1), + 'name' => $queryBuilder->createNamedParameter('endtoendencrypted'), + 'mimetype' => 4, + 'encrypted' => 0, + ]) + ->executeStatement(); + + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => 5, + 'parent' => 0, + 'path' => $queryBuilder->createNamedParameter('files/serversideencrypted'), + 'path_hash' => $queryBuilder->createNamedParameter(md5('files/serversideencrypted')), + 'storage' => $queryBuilder->createNamedParameter(1), + 'name' => $queryBuilder->createNamedParameter('serversideencrypted'), + 'mimetype' => 4, + 'encrypted' => 1, + ]) + ->executeStatement(); + + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => 6, + 'parent' => 0, + 'path' => $queryBuilder->createNamedParameter('files/storage2'), + 'path_hash' => $queryBuilder->createNamedParameter(md5('files/storage2')), + 'storage' => $queryBuilder->createNamedParameter(2), + 'name' => $queryBuilder->createNamedParameter('storage2'), + 'mimetype' => 5, + 'encrypted' => 0, + ]) + ->executeStatement(); + + $queryBuilder->insert('filecache') + ->values([ + 'fileid' => 7, + 'parent' => 6, + 'path' => $queryBuilder->createNamedParameter('files/storage2/file'), + 'path_hash' => $queryBuilder->createNamedParameter(md5('files/storage2/file')), + 'storage' => $queryBuilder->createNamedParameter(2), + 'name' => $queryBuilder->createNamedParameter('file'), + 'mimetype' => 6, + 'encrypted' => 0, + ]) + ->executeStatement(); + } + + /** + * Test fetching files by ancestor in storage. + */ + public function testGetByAncestorInStorage(): void { + $generator = $this->fileAccess->getByAncestorInStorage( + 1, // storageId + 1, // rootId + 0, // lastFileId + [], // mimeTypes + true, // include end-to-end encrypted files + true, // include server-side encrypted files + 10 // maxResults + ); + + $result = iterator_to_array($generator); + + $this->assertCount(4, $result); + + $paths = array_map(fn(CacheEntry $entry) => $entry->getPath(), $result); + $this->assertEquals([ + 'files/documents', + 'files/photos', + 'files/photos/endtoendencrypted', + 'files/serversideencrypted', + ], $paths); + } + + /** + * Test filtering by mime types. + */ + public function testGetByAncestorInStorageWithMimeTypes(): void { + $generator = $this->fileAccess->getByAncestorInStorage( + 1, + 1, + 0, + [2], // Only include documents (mimetype=2) + true, + true, + 10 + ); + + $result = iterator_to_array($generator); + + $this->assertCount(1, $result); + $this->assertEquals('files/documents', $result[0]->getPath()); + } + + /** + * Test excluding end-to-end encrypted files. + */ + public function testGetByAncestorInStorageWithoutEndToEndEncrypted(): void { + $generator = $this->fileAccess->getByAncestorInStorage( + 1, + 1, + 0, + [], + false, // exclude end-to-end encrypted files + true, + 10 + ); + + $result = iterator_to_array($generator); + + var_dump($result); + + $this->assertCount(1, $result); + $this->assertEquals('files/serversideencrypted', $result[0]->getPath()); + } + + /** + * Test excluding server-side encrypted files. + */ + public function testGetByAncestorInStorageWithoutServerSideEncrypted(): void { + $generator = $this->fileAccess->getByAncestorInStorage( + 1, + 1, + 0, + [], + true, + false, // exclude server-side encrypted files + 10 + ); + + $result = iterator_to_array($generator); + + $this->assertCount(1, $result); + $this->assertEquals('files/photos/endtoendencrypted', $result[0]->getPath()); + } + + /** + * Test max result limits. + */ + public function testGetByAncestorInStorageWithMaxResults(): void { + $generator = $this->fileAccess->getByAncestorInStorage( + 1, + 1, + 0, + [], + true, + true, + 1 // Limit to 1 result + ); + + $result = iterator_to_array($generator); + + $this->assertCount(1, $result); + $this->assertEquals('files/documents', $result[0]->getPath()); + } + + /** + * Test rootId filter + */ + public function testGetByAncestorInStorageWithRootIdFilter(): void { + $generator = $this->fileAccess->getByAncestorInStorage( + 1, + 3, // Filter by rootId + 0, + [], + true, + true, + 10 + ); + + $result = iterator_to_array($generator); + + $this->assertCount(1, $result); + $this->assertEquals('files/photos/endtoendencrypted', $result[0]->getPath()); + } + + /** + * Test rootId filter + */ + public function testGetByAncestorInStorageWithStorageFilter(): void { + $generator = $this->fileAccess->getByAncestorInStorage( + 2, // Filter by storage + 6, // and by rootId + 0, + [], + true, + true, + 10 + ); + + $result = iterator_to_array($generator); + + $this->assertCount(1, $result); + $this->assertEquals('files/storage2/file', $result[0]->getPath()); + } +}