Merge pull request #57228 from nextcloud/fix/setup-for-path-caching
fix: adjust authoritative setup path caching logicpull/56779/merge
commit
be8b2bfa8b
@ -0,0 +1,500 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Test\Files;
|
||||
|
||||
use OC\Files\Cache\CacheEntry;
|
||||
use OC\Files\Cache\FileAccess;
|
||||
use OC\Files\Config\MountProviderCollection;
|
||||
use OC\Files\SetupManager;
|
||||
use OC\Share20\ShareDisableChecker;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Diagnostics\IEventLogger;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Config\ICachedMountInfo;
|
||||
use OCP\Files\Config\IMountProvider;
|
||||
use OCP\Files\Config\IMountProviderArgs;
|
||||
use OCP\Files\Config\IPartialMountProvider;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\Mount\IMountManager;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\Storage\IStorageFactory;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Lockdown\ILockdownManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\MockObject\Rule\InvokedCount;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class SetupManagerTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @var (object&\PHPUnit\Framework\MockObject\MockObject)|IUserManager|(IUserManager&object&\PHPUnit\Framework\MockObject\MockObject)|(IUserManager&\PHPUnit\Framework\MockObject\MockObject)|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private IUserManager&MockObject $userManager;
|
||||
private IUserMountCache&MockObject $userMountCache;
|
||||
private ICache&MockObject $cache;
|
||||
private FileAccess&MockObject $fileAccess;
|
||||
private MountProviderCollection&MockObject $mountProviderCollection;
|
||||
private IMountManager&MockObject $mountManager;
|
||||
private SetupManager $setupManager;
|
||||
private IUser&MockObject $user;
|
||||
private string $userId;
|
||||
private string $path;
|
||||
private string $mountPoint;
|
||||
|
||||
protected function setUp(): void {
|
||||
$eventLogger = $this->createMock(IEventLogger::class);
|
||||
$eventLogger->method('start');
|
||||
$eventLogger->method('end');
|
||||
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->cache = $this->createMock(ICache::class);
|
||||
|
||||
$this->userId = 'alice';
|
||||
$this->path = "/{$this->userId}/files/folder";
|
||||
$this->mountPoint = "{$this->path}/";
|
||||
|
||||
$this->user = $this->createMock(IUser::class);
|
||||
$this->user->method('getUID')->willReturn($this->userId);
|
||||
$this->userManager->method('get')
|
||||
->with($this->userId)
|
||||
->willReturn($this->user);
|
||||
|
||||
// avoid triggering full setup required check
|
||||
$this->cache->method('get')
|
||||
->with($this->userId)
|
||||
->willReturn(true);
|
||||
|
||||
$this->mountProviderCollection = $this->createMock(MountProviderCollection::class);
|
||||
$this->mountManager = $this->createMock(IMountManager::class);
|
||||
$eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$eventDispatcher->method('addListener');
|
||||
$this->userMountCache = $this->createMock(IUserMountCache::class);
|
||||
$lockdownManager = $this->createMock(ILockdownManager::class);
|
||||
$userSession = $this->createMock(IUserSession::class);
|
||||
$cacheFactory = $this->createMock(ICacheFactory::class);
|
||||
$cacheFactory->expects($this->once())
|
||||
->method('createDistributed')
|
||||
->with('setupmanager::')
|
||||
->willReturn($this->cache);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$config = $this->createMock(IConfig::class);
|
||||
$config->method('getSystemValueBool')->willReturn(false);
|
||||
$shareDisableChecker = $this->createMock(ShareDisableChecker::class);
|
||||
$appManager = $this->createMock(IAppManager::class);
|
||||
$this->fileAccess = $this->createMock(FileAccess::class);
|
||||
|
||||
$lockdownManager->method('canAccessFilesystem')->willReturn(true);
|
||||
|
||||
$this->setupManager = new SetupManager(
|
||||
$eventLogger,
|
||||
$this->mountProviderCollection,
|
||||
$this->mountManager,
|
||||
$this->userManager,
|
||||
$eventDispatcher,
|
||||
$this->userMountCache,
|
||||
$lockdownManager,
|
||||
$userSession,
|
||||
$cacheFactory,
|
||||
$logger,
|
||||
$config,
|
||||
$shareDisableChecker,
|
||||
$appManager,
|
||||
$this->fileAccess,
|
||||
);
|
||||
}
|
||||
|
||||
public function testTearDown(): void {
|
||||
$this->setupManager->tearDown();
|
||||
}
|
||||
|
||||
public function testSetupForPathWithPartialProviderSkipsAlreadySetupPath(): void {
|
||||
$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
|
||||
|
||||
$this->userMountCache->expects($this->exactly(2))
|
||||
->method('getMountForPath')
|
||||
->with($this->user, $this->path)
|
||||
->willReturn($cachedMount);
|
||||
$this->userMountCache->expects($this->never())->method('registerMounts');
|
||||
$this->userMountCache->expects($this->never())->method('getMountsInPath');
|
||||
|
||||
$this->fileAccess->expects($this->once())
|
||||
->method('getByFileId')
|
||||
->with(42)
|
||||
->willReturn($this->createMock(CacheEntry::class));
|
||||
|
||||
$partialMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getUserMountsFromProviderByPath')
|
||||
->with(
|
||||
SetupManagerTestPartialMountProvider::class,
|
||||
$this->path,
|
||||
$this->callback(function (array $args) use ($cachedMount) {
|
||||
$this->assertCount(1, $args);
|
||||
$this->assertInstanceOf(IMountProviderArgs::class, $args[0]);
|
||||
$this->assertSame($cachedMount, $args[0]->mountInfo);
|
||||
return true;
|
||||
})
|
||||
)
|
||||
->willReturn([$partialMount]);
|
||||
|
||||
$homeMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getHomeMountForUser')
|
||||
->willReturn($homeMount);
|
||||
|
||||
$this->mountProviderCollection->expects($this->never())
|
||||
->method('getUserMountsForProviderClasses');
|
||||
|
||||
$invokedCount = $this->exactly(2);
|
||||
$addMountExpectations = [
|
||||
1 => $homeMount,
|
||||
2 => $partialMount,
|
||||
];
|
||||
$this->mountManager->expects($invokedCount)
|
||||
->method('addMount')
|
||||
->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
|
||||
|
||||
// setup called twice, provider should only be called once
|
||||
$this->setupManager->setupForPath($this->path, false);
|
||||
$this->setupManager->setupForPath($this->path, false);
|
||||
}
|
||||
|
||||
public function testSetupForPathWithNonPartialProviderSkipsAlreadySetupProvider(): void {
|
||||
$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42,
|
||||
IMountProvider::class);
|
||||
|
||||
$this->userMountCache->expects($this->exactly(2))
|
||||
->method('getMountForPath')
|
||||
->with($this->user, $this->path)
|
||||
->willReturn($cachedMount);
|
||||
|
||||
$this->userMountCache->expects($this->once())->method('registerMounts');
|
||||
$this->userMountCache->expects($this->never())->method('getMountsInPath');
|
||||
|
||||
$providerMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getUserMountsForProviderClasses')
|
||||
->with($this->user, [IMountProvider::class])
|
||||
->willReturn([$providerMount]);
|
||||
|
||||
$homeMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getHomeMountForUser')
|
||||
->willReturn($homeMount);
|
||||
|
||||
$invokedCount = $this->exactly(2);
|
||||
$addMountExpectations = [
|
||||
1 => $homeMount,
|
||||
2 => $providerMount,
|
||||
];
|
||||
$this->mountManager->expects($invokedCount)
|
||||
->method('addMount')
|
||||
->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
|
||||
|
||||
// setup called twice, provider should only be called once
|
||||
$this->setupManager->setupForPath($this->path, false);
|
||||
$this->setupManager->setupForPath($this->path, false);
|
||||
}
|
||||
|
||||
public function testSetupForPathWithChildrenAndNonPartialProviderSkipsAlreadySetupProvider(): void {
|
||||
$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42, IMountProvider::class);
|
||||
$additionalCachedMount = $this->getCachedMountInfo($this->mountPoint . 'additional/', 43, SetupManagerTestFullMountProvider::class);
|
||||
|
||||
$this->userMountCache->expects($this->exactly(2))
|
||||
->method('getMountForPath')
|
||||
->with($this->user, $this->path)
|
||||
->willReturn($cachedMount);
|
||||
|
||||
$this->userMountCache->expects($this->once())->method('registerMounts');
|
||||
$this->userMountCache->expects($this->once())->method('getMountsInPath')
|
||||
->willReturn([$additionalCachedMount]);
|
||||
|
||||
$mount = $this->createMock(IMountPoint::class);
|
||||
$additionalMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$invokedCount = $this->exactly(2);
|
||||
$this->mountProviderCollection->expects($invokedCount)
|
||||
->method('getUserMountsForProviderClasses')
|
||||
->willReturnCallback(function (IUser $userArg, array $providersArg) use (
|
||||
$additionalMount,
|
||||
$mount,
|
||||
$invokedCount) {
|
||||
if ($invokedCount->numberOfInvocations() === 1) {
|
||||
$providers = [IMountProvider::class];
|
||||
$returnMounts = [$mount];
|
||||
} else {
|
||||
$providers = [SetupManagerTestFullMountProvider::class];
|
||||
$returnMounts = [$additionalMount];
|
||||
}
|
||||
|
||||
$this->assertSame($this->user, $userArg);
|
||||
$this->assertSame($providersArg, $providers);
|
||||
|
||||
return $returnMounts;
|
||||
});
|
||||
|
||||
$homeMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getHomeMountForUser')
|
||||
->willReturn($homeMount);
|
||||
|
||||
$invokedCount = $this->exactly(3);
|
||||
$addMountExpectations = [
|
||||
1 => $homeMount,
|
||||
2 => $mount,
|
||||
3 => $additionalMount,
|
||||
];
|
||||
$this->mountManager->expects($invokedCount)
|
||||
->method('addMount')
|
||||
->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
|
||||
|
||||
// setup called twice, provider should only be called once
|
||||
$this->setupManager->setupForPath($this->path, true);
|
||||
$this->setupManager->setupForPath($this->path, false);
|
||||
}
|
||||
|
||||
public function testSetupForPathWithChildrenAndPartialProviderSkipsIfParentAlreadySetup(): void {
|
||||
$childPath = "{$this->path}/child";
|
||||
$childMountPoint = "{$childPath}/";
|
||||
|
||||
$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
|
||||
$cachedChildMount = $this->getCachedMountInfo($childMountPoint, 43);
|
||||
|
||||
$invokedCount = $this->exactly(3);
|
||||
$this->userMountCache->expects($invokedCount)
|
||||
->method('getMountForPath')
|
||||
->willReturnCallback(function (IUser $userArg, string $pathArg) use (
|
||||
$cachedChildMount,
|
||||
$cachedMount,
|
||||
$childPath,
|
||||
$invokedCount) {
|
||||
if ($invokedCount->numberOfInvocations() === 1) {
|
||||
$expectedPath = $this->path;
|
||||
$returnMount = $cachedMount;
|
||||
} else {
|
||||
$expectedPath = $childPath;
|
||||
$returnMount = $cachedChildMount;
|
||||
}
|
||||
|
||||
$this->assertSame($this->user, $userArg);
|
||||
$this->assertSame($expectedPath, $pathArg);
|
||||
|
||||
return $returnMount;
|
||||
});
|
||||
|
||||
$this->userMountCache->expects($this->never())->method('registerMounts');
|
||||
$this->userMountCache->expects($this->exactly(2))
|
||||
->method('getMountsInPath')
|
||||
->willReturn([$cachedChildMount]);
|
||||
|
||||
$this->fileAccess->expects($this->once())
|
||||
->method('getByFileId')
|
||||
->with(42)
|
||||
->willReturn($this->createMock(CacheEntry::class));
|
||||
|
||||
$this->fileAccess->expects($this->once())
|
||||
->method('getByFileIds')
|
||||
->with([43])
|
||||
->willReturn([43 => $this->createMock(CacheEntry::class)]);
|
||||
|
||||
$partialMount = $this->createMock(IMountPoint::class);
|
||||
$partialChildMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$invokedCount = $this->exactly(2);
|
||||
$this->mountProviderCollection->expects($invokedCount)
|
||||
->method('getUserMountsFromProviderByPath')
|
||||
->willReturnCallback(function (
|
||||
string $providerClass,
|
||||
string $pathArg,
|
||||
array $mountProviderArgs,
|
||||
) use (
|
||||
$cachedChildMount,
|
||||
$partialMount,
|
||||
$partialChildMount,
|
||||
$cachedMount,
|
||||
$invokedCount
|
||||
) {
|
||||
$expectedPath = $this->path;
|
||||
if ($invokedCount->numberOfInvocations() === 1) {
|
||||
// call for the parent
|
||||
$expectedCachedMount = $cachedMount;
|
||||
$mountPoints = [$partialMount];
|
||||
} else {
|
||||
// call for the children
|
||||
$expectedCachedMount = $cachedChildMount;
|
||||
$mountPoints = [$partialChildMount];
|
||||
}
|
||||
|
||||
$this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
|
||||
$this->assertSame($expectedPath, $pathArg);
|
||||
$this->assertCount(1, $mountProviderArgs);
|
||||
$this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
|
||||
$this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
|
||||
|
||||
return $mountPoints;
|
||||
});
|
||||
|
||||
$homeMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getHomeMountForUser')
|
||||
->willReturn($homeMount);
|
||||
|
||||
$this->mountProviderCollection->expects($this->never())
|
||||
->method('getUserMountsForProviderClasses');
|
||||
|
||||
$invokedCount = $this->exactly(3);
|
||||
$addMountExpectations = [
|
||||
1 => $homeMount,
|
||||
2 => $partialMount,
|
||||
3 => $partialChildMount,
|
||||
];
|
||||
$this->mountManager->expects($invokedCount)
|
||||
->method('addMount')
|
||||
->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
|
||||
|
||||
// once the setup for a path has been done with children, setup for sub
|
||||
// paths should not create the same new mounts again
|
||||
$this->setupManager->setupForPath($this->path, true);
|
||||
$this->setupManager->setupForPath($childPath, false);
|
||||
$this->setupManager->setupForPath($childPath, true);
|
||||
}
|
||||
|
||||
public function testSetupForPathHandlesPartialAndFullProvidersWithChildren(): void {
|
||||
$parentPartialCachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
|
||||
$childCachedPartialMount = $this->getCachedMountInfo("{$this->mountPoint}partial/", 43);
|
||||
$childCachedFullMount = $this->getCachedMountInfo("{$this->mountPoint}full/", 44, SetupManagerTestFullMountProvider::class);
|
||||
|
||||
$this->userMountCache->expects($this->exactly(2))
|
||||
->method('getMountForPath')
|
||||
->with($this->user, $this->path)
|
||||
->willReturn($parentPartialCachedMount);
|
||||
$this->userMountCache->expects($this->exactly(2))
|
||||
->method('getMountsInPath')
|
||||
->with($this->user, $this->path)
|
||||
->willReturn([$childCachedPartialMount, $childCachedFullMount]);
|
||||
|
||||
$homeMount = $this->createMock(IMountPoint::class);
|
||||
$parentPartialMount = $this->createMock(IMountPoint::class);
|
||||
$childPartialMount = $this->createMock(IMountPoint::class);
|
||||
$childFullProviderMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getHomeMountForUser')
|
||||
->willReturn($homeMount);
|
||||
|
||||
$this->userMountCache->expects($this->once())
|
||||
->method('registerMounts')
|
||||
->with(
|
||||
$this->user, [$childFullProviderMount],
|
||||
[SetupManagerTestFullMountProvider::class],
|
||||
);
|
||||
|
||||
$this->fileAccess->expects($this->once())
|
||||
->method('getByFileId')
|
||||
->with(42)
|
||||
->willReturn($this->createMock(CacheEntry::class));
|
||||
$childMetadata = $this->createMock(CacheEntry::class);
|
||||
$this->fileAccess->expects($this->once())
|
||||
->method('getByFileIds')
|
||||
->with([43])
|
||||
->willReturn([43 => $childMetadata]);
|
||||
|
||||
$invokedCount = $this->exactly(2);
|
||||
$this->mountProviderCollection->expects($invokedCount)
|
||||
->method('getUserMountsFromProviderByPath')
|
||||
->willReturnCallback(function (string $providerClass, string $pathArg, array $mountProviderArgs) use (
|
||||
$childCachedPartialMount,
|
||||
$childPartialMount,
|
||||
$parentPartialMount,
|
||||
$parentPartialCachedMount,
|
||||
$invokedCount) {
|
||||
$expectedPath = $this->path;
|
||||
if ($invokedCount->numberOfInvocations() === 1) {
|
||||
// call for the parent
|
||||
$expectedCachedMount = $parentPartialCachedMount;
|
||||
$mountPoints = [$parentPartialMount];
|
||||
} else {
|
||||
// call for the children
|
||||
$expectedCachedMount = $childCachedPartialMount;
|
||||
$mountPoints = [$childPartialMount];
|
||||
}
|
||||
|
||||
$this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
|
||||
$this->assertSame($expectedPath, $pathArg);
|
||||
$this->assertCount(1, $mountProviderArgs);
|
||||
$this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
|
||||
$this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
|
||||
|
||||
return $mountPoints;
|
||||
});
|
||||
|
||||
$this->mountProviderCollection->expects($this->once())
|
||||
->method('getUserMountsForProviderClasses')
|
||||
->with($this->user, [SetupManagerTestFullMountProvider::class])
|
||||
->willReturn([$childFullProviderMount]);
|
||||
|
||||
$invokedCount = $this->exactly(4);
|
||||
$addMountExpectations = [
|
||||
1 => $homeMount,
|
||||
2 => $childFullProviderMount,
|
||||
3 => $parentPartialMount,
|
||||
4 => $childPartialMount,
|
||||
];
|
||||
$this->mountManager->expects($invokedCount)
|
||||
->method('addMount')
|
||||
->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
|
||||
|
||||
// call twice to test that providers and mounts are only called once
|
||||
$this->setupManager->setupForPath($this->path, true);
|
||||
$this->setupManager->setupForPath($this->path, true);
|
||||
}
|
||||
|
||||
private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
|
||||
return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
|
||||
$expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;
|
||||
$this->assertSame($expectedMount, $actualMount);
|
||||
};
|
||||
}
|
||||
|
||||
public function getCachedMountInfo(string $mountPoint, int $rootId, string $providerClass = SetupManagerTestPartialMountProvider::class): ICachedMountInfo&MockObject {
|
||||
$cachedMount = $this->createMock(ICachedMountInfo::class);
|
||||
$cachedMount->method('getMountProvider')->willReturn($providerClass);
|
||||
$cachedMount->method('getMountPoint')->willReturn($mountPoint);
|
||||
$cachedMount->method('getRootId')->willReturn($rootId);
|
||||
|
||||
return $cachedMount;
|
||||
}
|
||||
}
|
||||
|
||||
class SetupManagerTestPartialMountProvider implements IPartialMountProvider {
|
||||
public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getMountsForPath(string $path, array $mountProviderArgs, IStorageFactory $loader): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class SetupManagerTestFullMountProvider implements IMountProvider {
|
||||
public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue