diff options
Diffstat (limited to 'apps/files_external/tests/Service/StoragesServiceTestCase.php')
-rw-r--r-- | apps/files_external/tests/Service/StoragesServiceTestCase.php | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/apps/files_external/tests/Service/StoragesServiceTestCase.php b/apps/files_external/tests/Service/StoragesServiceTestCase.php new file mode 100644 index 00000000000..b41eb409468 --- /dev/null +++ b/apps/files_external/tests/Service/StoragesServiceTestCase.php @@ -0,0 +1,499 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Tests\Service; + +use OC\Files\Cache\Storage; +use OC\Files\Filesystem; +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\Auth\InvalidAuth; +use OCA\Files_External\Lib\Auth\NullMechanism; +use OCA\Files_External\Lib\Backend\Backend; +use OCA\Files_External\Lib\Backend\InvalidBackend; +use OCA\Files_External\Lib\Backend\SMB; +use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\MountConfig; +use OCA\Files_External\NotFoundException; +use OCA\Files_External\Service\BackendService; +use OCA\Files_External\Service\DBConfigService; +use OCA\Files_External\Service\StoragesService; +use OCP\AppFramework\IAppContainer; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Cache\ICache; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorage; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IUser; +use OCP\Security\ICrypto; +use OCP\Server; +use OCP\Util; +use PHPUnit\Framework\MockObject\MockObject; + +class CleaningDBConfig extends DBConfigService { + private array $mountIds = []; + + public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) { + $id = parent::addMount($mountPoint, $storageBackend, $authBackend, $priority, $type); // TODO: Change the autogenerated stub + $this->mountIds[] = $id; + return $id; + } + + public function clean() { + foreach ($this->mountIds as $id) { + $this->removeMount($id); + } + } +} + +/** + * @group DB + */ +abstract class StoragesServiceTestCase extends \Test\TestCase { + protected StoragesService $service; + protected BackendService $backendService; + protected string $dataDir; + protected CleaningDBConfig $dbConfig; + protected static array $hookCalls; + protected IUserMountCache&MockObject $mountCache; + protected IEventDispatcher&MockObject $eventDispatcher; + + protected function setUp(): void { + parent::setUp(); + $this->dbConfig = new CleaningDBConfig(Server::get(IDBConnection::class), Server::get(ICrypto::class)); + self::$hookCalls = []; + $config = Server::get(IConfig::class); + $this->dataDir = $config->getSystemValue( + 'datadirectory', + \OC::$SERVERROOT . '/data/' + ); + MountConfig::$skipTest = true; + + $this->mountCache = $this->createMock(IUserMountCache::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); + + // prepare BackendService mock + $this->backendService = $this->createMock(BackendService::class); + + $authMechanisms = [ + 'identifier:\Auth\Mechanism' => $this->getAuthMechMock('null', '\Auth\Mechanism'), + 'identifier:\Other\Auth\Mechanism' => $this->getAuthMechMock('null', '\Other\Auth\Mechanism'), + 'identifier:\OCA\Files_External\Lib\Auth\NullMechanism' => $this->getAuthMechMock(), + ]; + $this->backendService->method('getAuthMechanism') + ->willReturnCallback(function ($class) use ($authMechanisms) { + if (isset($authMechanisms[$class])) { + return $authMechanisms[$class]; + } + return null; + }); + $this->backendService->method('getAuthMechanismsByScheme') + ->willReturnCallback(function ($schemes) use ($authMechanisms) { + return array_filter($authMechanisms, function ($authMech) use ($schemes) { + return in_array($authMech->getScheme(), $schemes, true); + }); + }); + $this->backendService->method('getAuthMechanisms') + ->willReturn($authMechanisms); + + $sftpBackend = $this->getBackendMock('\OCA\Files_External\Lib\Backend\SFTP', '\OCA\Files_External\Lib\Storage\SFTP'); + $backends = [ + 'identifier:\OCA\Files_External\Lib\Backend\DAV' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\DAV', '\OC\Files\Storage\DAV'), + 'identifier:\OCA\Files_External\Lib\Backend\SMB' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\SMB', '\OCA\Files_External\Lib\Storage\SMB'), + 'identifier:\OCA\Files_External\Lib\Backend\SFTP' => $sftpBackend, + 'identifier:sftp_alias' => $sftpBackend, + ]; + $backends['identifier:\OCA\Files_External\Lib\Backend\SFTP']->method('getLegacyAuthMechanism') + ->willReturn($authMechanisms['identifier:\Other\Auth\Mechanism']); + $this->backendService->method('getBackend') + ->willReturnCallback(function ($backendClass) use ($backends) { + if (isset($backends[$backendClass])) { + return $backends[$backendClass]; + } + return null; + }); + $this->backendService->method('getBackends') + ->willReturn($backends); + $this->overwriteService(BackendService::class, $this->backendService); + + Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_create_mount, + get_class($this), 'createHookCallback'); + Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_delete_mount, + get_class($this), 'deleteHookCallback'); + + $containerMock = $this->createMock(IAppContainer::class); + $containerMock->method('query') + ->willReturnCallback(function ($name) { + if ($name === 'OCA\Files_External\Service\BackendService') { + return $this->backendService; + } + }); + } + + protected function tearDown(): void { + MountConfig::$skipTest = false; + self::$hookCalls = []; + if ($this->dbConfig) { + $this->dbConfig->clean(); + } + parent::tearDown(); + } + + protected function getBackendMock($class = SMB::class, $storageClass = \OCA\Files_External\Lib\Storage\SMB::class) { + $backend = $this->createMock(Backend::class); + $backend->method('getStorageClass') + ->willReturn($storageClass); + $backend->method('getIdentifier') + ->willReturn('identifier:' . $class); + return $backend; + } + + protected function getAuthMechMock($scheme = 'null', $class = NullMechanism::class) { + $authMech = $this->createMock(AuthMechanism::class); + $authMech->method('getScheme') + ->willReturn($scheme); + $authMech->method('getIdentifier') + ->willReturn('identifier:' . $class); + + return $authMech; + } + + /** + * Creates a StorageConfig instance based on array data + */ + protected function makeStorageConfig(array $data): StorageConfig { + $storage = new StorageConfig(); + if (isset($data['id'])) { + $storage->setId($data['id']); + } + $storage->setMountPoint($data['mountPoint']); + if (!isset($data['backend'])) { + // data providers are run before $this->backendService is initialised + // so $data['backend'] can be specified directly + $data['backend'] = $this->backendService->getBackend($data['backendIdentifier']); + } + if (!isset($data['backend'])) { + throw new \Exception('oops, no backend'); + } + if (!isset($data['authMechanism'])) { + $data['authMechanism'] = $this->backendService->getAuthMechanism($data['authMechanismIdentifier']); + } + if (!isset($data['authMechanism'])) { + throw new \Exception('oops, no auth mechanism'); + } + $storage->setBackend($data['backend']); + $storage->setAuthMechanism($data['authMechanism']); + $storage->setBackendOptions($data['backendOptions']); + if (isset($data['applicableUsers'])) { + $storage->setApplicableUsers($data['applicableUsers']); + } + if (isset($data['applicableGroups'])) { + $storage->setApplicableGroups($data['applicableGroups']); + } + if (isset($data['priority'])) { + $storage->setPriority($data['priority']); + } + if (isset($data['mountOptions'])) { + $storage->setMountOptions($data['mountOptions']); + } + return $storage; + } + + + protected function ActualNonExistingStorageTest() { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $this->service->updateStorage($storage); + } + + public function testNonExistingStorage(): void { + $this->expectException(NotFoundException::class); + + $this->ActualNonExistingStorageTest(); + } + + public static function deleteStorageDataProvider(): array { + return [ + // regular case, can properly delete the oc_storages entry + [ + [ + 'host' => 'example.com', + 'user' => 'test', + 'password' => 'testPassword', + 'root' => 'someroot', + ], + 'webdav::test@example.com//someroot/' + ], + [ + [ + 'host' => 'example.com', + 'user' => '$user', + 'password' => 'testPassword', + 'root' => 'someroot', + ], + 'webdav::someone@example.com//someroot/' + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('deleteStorageDataProvider')] + public function testDeleteStorage(array $backendOptions, string $rustyStorageId): void { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\DAV'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions($backendOptions); + + $newStorage = $this->service->addStorage($storage); + $id = $newStorage->getId(); + + // manually trigger storage entry because normally it happens on first + // access, which isn't possible within this test + $storageCache = new Storage($rustyStorageId, true, Server::get(IDBConnection::class)); + + /** @var IUserMountCache $mountCache */ + $mountCache = Server::get(IUserMountCache::class); + $mountCache->clear(); + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn('test'); + $cache = $this->createMock(ICache::class); + $storage = $this->createMock(IStorage::class); + $storage->method('getCache')->willReturn($cache); + $mount = $this->createMock(IMountPoint::class); + $mount->method('getStorage') + ->willReturn($storage); + $mount->method('getStorageId') + ->willReturn($rustyStorageId); + $mount->method('getNumericStorageId') + ->willReturn($storageCache->getNumericId()); + $mount->method('getStorageRootId') + ->willReturn(1); + $mount->method('getMountPoint') + ->willReturn('dummy'); + $mount->method('getMountId') + ->willReturn($id); + $mountCache->registerMounts($user, [ + $mount + ]); + + // get numeric id for later check + $numericId = $storageCache->getNumericId(); + + $this->service->removeStorage($id); + + $caught = false; + try { + $this->service->getStorage(1); + } catch (NotFoundException $e) { + $caught = true; + } + + $this->assertTrue($caught); + + // storage id was removed from oc_storages + $qb = Server::get(IDBConnection::class)->getQueryBuilder(); + $storageCheckQuery = $qb->select('*') + ->from('storages') + ->where($qb->expr()->eq('numeric_id', $qb->expr()->literal($numericId))); + + $result = $storageCheckQuery->execute(); + $storages = $result->fetchAll(); + $result->closeCursor(); + $this->assertCount(0, $storages, 'expected 0 storages, got ' . json_encode($storages)); + } + + protected function actualDeletedUnexistingStorageTest() { + $this->service->removeStorage(255); + } + + public function testDeleteUnexistingStorage(): void { + $this->expectException(NotFoundException::class); + + $this->actualDeletedUnexistingStorageTest(); + } + + public function testCreateStorage(): void { + $mountPoint = 'mount'; + $backendIdentifier = 'identifier:\OCA\Files_External\Lib\Backend\SMB'; + $authMechanismIdentifier = 'identifier:\Auth\Mechanism'; + $backendOptions = ['param' => 'foo', 'param2' => 'bar']; + $mountOptions = ['option' => 'foobar']; + $applicableUsers = ['user1', 'user2']; + $applicableGroups = ['group']; + $priority = 123; + + $backend = $this->backendService->getBackend($backendIdentifier); + $authMechanism = $this->backendService->getAuthMechanism($authMechanismIdentifier); + + $storage = $this->service->createStorage( + $mountPoint, + $backendIdentifier, + $authMechanismIdentifier, + $backendOptions, + $mountOptions, + $applicableUsers, + $applicableGroups, + $priority + ); + + $this->assertEquals('/' . $mountPoint, $storage->getMountPoint()); + $this->assertEquals($backend, $storage->getBackend()); + $this->assertEquals($authMechanism, $storage->getAuthMechanism()); + $this->assertEquals($backendOptions, $storage->getBackendOptions()); + $this->assertEquals($mountOptions, $storage->getMountOptions()); + $this->assertEquals($applicableUsers, $storage->getApplicableUsers()); + $this->assertEquals($applicableGroups, $storage->getApplicableGroups()); + $this->assertEquals($priority, $storage->getPriority()); + } + + public function testCreateStorageInvalidClass(): void { + $storage = $this->service->createStorage( + 'mount', + 'identifier:\OC\Not\A\Backend', + 'identifier:\Auth\Mechanism', + [] + ); + $this->assertInstanceOf(InvalidBackend::class, $storage->getBackend()); + } + + public function testCreateStorageInvalidAuthMechanismClass(): void { + $storage = $this->service->createStorage( + 'mount', + 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'identifier:\Not\An\Auth\Mechanism', + [] + ); + $this->assertInstanceOf(InvalidAuth::class, $storage->getAuthMechanism()); + } + + public function testGetStoragesBackendNotVisible(): void { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $backend->expects($this->once()) + ->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(false); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + $authMechanism->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(true); + + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $newStorage = $this->service->addStorage($storage); + + $this->assertCount(1, $this->service->getAllStorages()); + $this->assertEmpty($this->service->getStorages()); + } + + public function testGetStoragesAuthMechanismNotVisible(): void { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $backend->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(true); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + $authMechanism->expects($this->once()) + ->method('isVisibleFor') + ->with($this->service->getVisibilityType()) + ->willReturn(false); + + $storage = new StorageConfig(255); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $newStorage = $this->service->addStorage($storage); + + $this->assertCount(1, $this->service->getAllStorages()); + $this->assertEmpty($this->service->getStorages()); + } + + public static function createHookCallback($params): void { + self::$hookCalls[] = [ + 'signal' => Filesystem::signal_create_mount, + 'params' => $params + ]; + } + + public static function deleteHookCallback($params): void { + self::$hookCalls[] = [ + 'signal' => Filesystem::signal_delete_mount, + 'params' => $params + ]; + } + + /** + * Asserts hook call + * + * @param array $callData hook call data to check + * @param string $signal signal name + * @param string $mountPath mount path + * @param string $mountType mount type + * @param string $applicable applicable users + */ + protected function assertHookCall($callData, $signal, $mountPath, $mountType, $applicable) { + $this->assertEquals($signal, $callData['signal']); + $params = $callData['params']; + $this->assertEquals( + $mountPath, + $params[Filesystem::signal_param_path] + ); + $this->assertEquals( + $mountType, + $params[Filesystem::signal_param_mount_type] + ); + $this->assertEquals( + $applicable, + $params[Filesystem::signal_param_users] + ); + } + + public function testUpdateStorageMountPoint(): void { + $backend = $this->backendService->getBackend('identifier:\OCA\Files_External\Lib\Backend\SMB'); + $authMechanism = $this->backendService->getAuthMechanism('identifier:\Auth\Mechanism'); + + $storage = new StorageConfig(); + $storage->setMountPoint('mountpoint'); + $storage->setBackend($backend); + $storage->setAuthMechanism($authMechanism); + $storage->setBackendOptions(['password' => 'testPassword']); + + $savedStorage = $this->service->addStorage($storage); + + $newAuthMechanism = $this->backendService->getAuthMechanism('identifier:\Other\Auth\Mechanism'); + + $updatedStorage = new StorageConfig($savedStorage->getId()); + $updatedStorage->setMountPoint('mountpoint2'); + $updatedStorage->setBackend($backend); + $updatedStorage->setAuthMechanism($newAuthMechanism); + $updatedStorage->setBackendOptions(['password' => 'password2']); + + $this->service->updateStorage($updatedStorage); + + $savedStorage = $this->service->getStorage($updatedStorage->getId()); + + $this->assertEquals('/mountpoint2', $savedStorage->getMountPoint()); + $this->assertEquals($newAuthMechanism, $savedStorage->getAuthMechanism()); + $this->assertEquals('password2', $savedStorage->getBackendOption('password')); + } +} |