fix(FileAccess): Add tests

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
pull/51810/head
Marcel Klehr 2025-04-01 17:56:05 +07:00
parent 5689af5a94
commit d67c877ac5
2 changed files with 447 additions and 9 deletions

@ -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 (

@ -0,0 +1,438 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace Test\Files\Cache;
use OC\Files\Cache\CacheEntry;
use OC\Files\Cache\FileAccess;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use Psr\Log\LoggerInterface;
use Test\TestCase;
/**
* @group DB
*/
class FileAccessTest extends TestCase {
/** @var IDBConnection */
private $dbConnection;
/** @var FileAccess */
private $fileAccess;
protected function setUp(): void {
parent::setUp();
// Setup the actual database connection (assume the database is configured properly in PHPUnit setup)
$this->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());
}
}