aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Files/Node
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Files/Node')
-rw-r--r--tests/lib/Files/Node/FileTest.php309
-rw-r--r--tests/lib/Files/Node/FolderTest.php1044
-rw-r--r--tests/lib/Files/Node/HookConnectorTest.php327
-rw-r--r--tests/lib/Files/Node/IntegrationTest.php151
-rw-r--r--tests/lib/Files/Node/NodeTestCase.php793
-rw-r--r--tests/lib/Files/Node/RootTest.php253
6 files changed, 2877 insertions, 0 deletions
diff --git a/tests/lib/Files/Node/FileTest.php b/tests/lib/Files/Node/FileTest.php
new file mode 100644
index 00000000000..eec34d156ad
--- /dev/null
+++ b/tests/lib/Files/Node/FileTest.php
@@ -0,0 +1,309 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Node;
+
+use OC\Files\Node\File;
+use OC\Files\Node\Root;
+use OCP\Constants;
+use OCP\Files\NotPermittedException;
+
+/**
+ * Class FileTest
+ *
+ * @group DB
+ *
+ * @package Test\Files\Node
+ */
+class FileTest extends NodeTestCase {
+ protected function createTestNode($root, $view, $path, array $data = [], $internalPath = '', $storage = null) {
+ if ($data || $internalPath || $storage) {
+ return new File($root, $view, $path, $this->getFileInfo($data, $internalPath, $storage));
+ } else {
+ return new File($root, $view, $path);
+ }
+ }
+
+ protected function getNodeClass() {
+ return '\OC\Files\Node\File';
+ }
+
+ protected function getNonExistingNodeClass() {
+ return '\OC\Files\Node\NonExistingFile';
+ }
+
+ protected function getViewDeleteMethod() {
+ return 'unlink';
+ }
+
+ public function testGetContent(): void {
+ /** @var \OC\Files\Node\Root|\PHPUnit\Framework\MockObject\MockObject $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$this->manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+
+ $hook = function ($file): void {
+ throw new \Exception('Hooks are not supposed to be called');
+ };
+
+ $root->listen('\OC\Files', 'preWrite', $hook);
+ $root->listen('\OC\Files', 'postWrite', $hook);
+
+ $this->view->expects($this->once())
+ ->method('file_get_contents')
+ ->with('/bar/foo')
+ ->willReturn('bar');
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $this->assertEquals('bar', $node->getContent());
+ }
+
+
+ public function testGetContentNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ /** @var \OC\Files\Node\Root|\PHPUnit\Framework\MockObject\MockObject $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$this->manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => 0]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $node->getContent();
+ }
+
+ public function testPutContent(): void {
+ /** @var \OC\Files\Node\Root|\PHPUnit\Framework\MockObject\MockObject $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$this->manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $this->view->expects($this->once())
+ ->method('file_put_contents')
+ ->with('/bar/foo', 'bar')
+ ->willReturn(true);
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $node->putContent('bar');
+ }
+
+
+ public function testPutContentNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ /** @var \OC\Files\Node\Root|\PHPUnit\Framework\MockObject\MockObject $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$this->manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $node->putContent('bar');
+ }
+
+ public function testGetMimeType(): void {
+ /** @var \OC\Files\Node\Root|\PHPUnit\Framework\MockObject\MockObject $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$this->manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['mimetype' => 'text/plain']));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $this->assertEquals('text/plain', $node->getMimeType());
+ }
+
+ public function testFOpenRead(): void {
+ $stream = fopen('php://memory', 'w+');
+ fwrite($stream, 'bar');
+ rewind($stream);
+
+ $root = new Root(
+ $this->manager,
+ $this->view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+
+ $hook = function ($file): void {
+ throw new \Exception('Hooks are not supposed to be called');
+ };
+
+ $root->listen('\OC\Files', 'preWrite', $hook);
+ $root->listen('\OC\Files', 'postWrite', $hook);
+
+ $this->view->expects($this->once())
+ ->method('fopen')
+ ->with('/bar/foo', 'r')
+ ->willReturn($stream);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $fh = $node->fopen('r');
+ $this->assertEquals($stream, $fh);
+ $this->assertEquals('bar', fread($fh, 3));
+ }
+
+ public function testFOpenWrite(): void {
+ $stream = fopen('php://memory', 'w+');
+
+ $root = new Root(
+ $this->manager,
+ $this->view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+ $hooksCalled = 0;
+ $hook = function ($file) use (&$hooksCalled): void {
+ $hooksCalled++;
+ };
+
+ $root->listen('\OC\Files', 'preWrite', $hook);
+ $root->listen('\OC\Files', 'postWrite', $hook);
+
+ $this->view->expects($this->once())
+ ->method('fopen')
+ ->with('/bar/foo', 'w')
+ ->willReturn($stream);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $fh = $node->fopen('w');
+ $this->assertEquals($stream, $fh);
+ fwrite($fh, 'bar');
+ rewind($fh);
+ $this->assertEquals('bar', fread($stream, 3));
+ $this->assertEquals(2, $hooksCalled);
+ }
+
+
+ public function testFOpenReadNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $root = new Root(
+ $this->manager,
+ $this->view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+ $hook = function ($file): void {
+ throw new \Exception('Hooks are not supposed to be called');
+ };
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => 0]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $node->fopen('r');
+ }
+
+
+ public function testFOpenReadWriteNoReadPermissions(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $root = new Root(
+ $this->manager,
+ $this->view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+ $hook = function (): void {
+ throw new \Exception('Hooks are not supposed to be called');
+ };
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_UPDATE]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $node->fopen('w');
+ }
+
+
+ public function testFOpenReadWriteNoWritePermissions(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $root = new Root(
+ $this->manager,
+ $this->view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+ $hook = function (): void {
+ throw new \Exception('Hooks are not supposed to be called');
+ };
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $node = new File($root, $this->view, '/bar/foo');
+ $node->fopen('w');
+ }
+}
diff --git a/tests/lib/Files/Node/FolderTest.php b/tests/lib/Files/Node/FolderTest.php
new file mode 100644
index 00000000000..439535cf2c1
--- /dev/null
+++ b/tests/lib/Files/Node/FolderTest.php
@@ -0,0 +1,1044 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Node;
+
+use OC\Files\Cache\Cache;
+use OC\Files\Cache\CacheEntry;
+use OC\Files\Config\CachedMountInfo;
+use OC\Files\FileInfo;
+use OC\Files\Mount\Manager;
+use OC\Files\Mount\MountPoint;
+use OC\Files\Node\File;
+use OC\Files\Node\Folder;
+use OC\Files\Node\Node;
+use OC\Files\Node\Root;
+use OC\Files\Search\SearchBinaryOperator;
+use OC\Files\Search\SearchComparison;
+use OC\Files\Search\SearchOrder;
+use OC\Files\Search\SearchQuery;
+use OC\Files\Storage\Storage;
+use OC\Files\Storage\Temporary;
+use OC\Files\Storage\Wrapper\Jail;
+use OC\Files\View;
+use OCP\Constants;
+use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\InvalidPathException;
+use OCP\Files\IRootFolder;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\Files\Search\ISearchBinaryOperator;
+use OCP\Files\Search\ISearchComparison;
+use OCP\Files\Search\ISearchOrder;
+use OCP\Files\Storage\IStorage;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * Class FolderTest
+ *
+ * @group DB
+ *
+ * @package Test\Files\Node
+ */
+class FolderTest extends NodeTestCase {
+ protected function createTestNode($root, $view, $path, array $data = [], $internalPath = '', $storage = null) {
+ $view->expects($this->any())
+ ->method('getRoot')
+ ->willReturn('');
+ if ($data || $internalPath || $storage) {
+ return new Folder($root, $view, $path, $this->getFileInfo($data, $internalPath, $storage));
+ } else {
+ return new Folder($root, $view, $path);
+ }
+ }
+
+ protected function getNodeClass() {
+ return '\OC\Files\Node\Folder';
+ }
+
+ protected function getNonExistingNodeClass() {
+ return '\OC\Files\Node\NonExistingFolder';
+ }
+
+ protected function getViewDeleteMethod() {
+ return 'rmdir';
+ }
+
+ public function testGetDirectoryContent(): void {
+ $manager = $this->createMock(Manager::class);
+ /**
+ * @var View|\PHPUnit\Framework\MockObject\MockObject $view
+ */
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $this->view->expects($this->any())
+ ->method('getDirectoryContent')
+ ->with('/bar/foo')
+ ->willReturn([
+ new FileInfo('/bar/foo/asd', null, 'foo/asd', ['fileid' => 2, 'path' => '/bar/foo/asd', 'name' => 'asd', 'size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain'], null),
+ new FileInfo('/bar/foo/qwerty', null, 'foo/qwerty', ['fileid' => 3, 'path' => '/bar/foo/qwerty', 'name' => 'qwerty', 'size' => 200, 'mtime' => 55, 'mimetype' => 'httpd/unix-directory'], null),
+ ]);
+ $this->view->method('getFileInfo')
+ ->willReturn($this->createMock(FileInfo::class));
+ $this->view->method('getRelativePath')
+ ->willReturn('/bar/foo');
+
+ $node = new Folder($root, $this->view, '/bar/foo');
+ $children = $node->getDirectoryListing();
+ $this->assertEquals(2, count($children));
+ $this->assertInstanceOf('\OC\Files\Node\File', $children[0]);
+ $this->assertInstanceOf('\OC\Files\Node\Folder', $children[1]);
+ $this->assertEquals('asd', $children[0]->getName());
+ $this->assertEquals('qwerty', $children[1]->getName());
+ $this->assertEquals(2, $children[0]->getId());
+ $this->assertEquals(3, $children[1]->getId());
+ }
+
+ public function testGet(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $node = new File($root, $view, '/bar/foo/asd');
+ $root->method('get')
+ ->with('/bar/foo/asd')
+ ->willReturn($node);
+
+ $parentNode = new Folder($root, $view, '/bar/foo');
+ self::assertEquals($node, $parentNode->get('asd'));
+ }
+
+ public function testNodeExists(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $child = new Folder($root, $view, '/bar/foo/asd');
+
+ $root->method('get')
+ ->with('/bar/foo/asd')
+ ->willReturn($child);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $this->assertTrue($node->nodeExists('asd'));
+ }
+
+ public function testNodeExistsNotExists(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $root->method('get')
+ ->with('/bar/foo/asd')
+ ->willThrowException(new NotFoundException());
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $this->assertFalse($node->nodeExists('asd'));
+ }
+
+ public function testNewFolder(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $view->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $view->method('mkdir')
+ ->with('/bar/foo/asd')
+ ->willReturn(true);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $child = new Folder($root, $view, '/bar/foo/asd', null, $node);
+ $result = $node->newFolder('asd');
+ $this->assertEquals($child, $result);
+ }
+
+ public function testNewFolderDeepParent(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $view->method('getFileInfo')
+ ->with('/foobar')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $view->method('mkdir')
+ ->with('/foobar/asd/sdf')
+ ->willReturn(true);
+
+ $node = new Folder($root, $view, '/foobar');
+ $child = new Folder($root, $view, '/foobar/asd/sdf', null, null);
+ $result = $node->newFolder('asd/sdf');
+ $this->assertEquals($child, $result);
+ }
+
+
+ public function testNewFolderNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->method('getUser')
+ ->willReturn($this->user);
+
+ $view->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $node->newFolder('asd');
+ }
+
+ public function testNewFile(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $view->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $view->method('touch')
+ ->with('/bar/foo/asd')
+ ->willReturn(true);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $child = new File($root, $view, '/bar/foo/asd', null, $node);
+ $result = $node->newFile('asd');
+ $this->assertEquals($child, $result);
+ }
+
+
+ public function testNewFileNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->method('getUser')
+ ->willReturn($this->user);
+
+ $view->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $node->newFile('asd');
+ }
+
+ public function testGetFreeSpace(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->method('getUser')
+ ->willReturn($this->user);
+
+ $view->method('free_space')
+ ->with('/bar/foo')
+ ->willReturn(100);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $this->assertEquals(100, $node->getFreeSpace());
+ }
+
+ public function testSearch(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->method('getUser')
+ ->willReturn($this->user);
+ /** @var Storage\IStorage&MockObject $storage */
+ $storage = $this->createMock(IStorage::class);
+ $storage->method('getId')->willReturn('test::1');
+ $cache = new Cache($storage);
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+
+ $storage->expects($this->atLeastOnce())
+ ->method('getOwner')
+ ->with('qwerty')
+ ->willReturn(false);
+
+ $mount = $this->createMock(IMountPoint::class);
+ $mount->expects($this->atLeastOnce())
+ ->method('getStorage')
+ ->willReturn($storage);
+ $mount->expects($this->atLeastOnce())
+ ->method('getInternalPath')
+ ->willReturn('foo');
+
+ $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
+
+ $root->method('getMountsIn')
+ ->with('/bar/foo')
+ ->willReturn([]);
+
+ $root->method('getMount')
+ ->with('/bar/foo')
+ ->willReturn($mount);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $result = $node->search('qw');
+ $cache->clear();
+ $this->assertEquals(1, count($result));
+ $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
+ }
+
+ public function testSearchInRoot(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getUser', 'getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+ /** @var \PHPUnit\Framework\MockObject\MockObject|Storage $storage */
+ $storage = $this->createMock(IStorage::class);
+ $storage->method('getId')->willReturn('test::2');
+ $cache = new Cache($storage);
+
+ $mount = $this->createMock(IMountPoint::class);
+ $mount->method('getStorage')
+ ->willReturn($storage);
+ $mount->method('getInternalPath')
+ ->willReturn('files');
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('files', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('files/foo', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
+
+ $root->method('getMountsIn')
+ ->with('')
+ ->willReturn([]);
+
+ $root->method('getMount')
+ ->with('')
+ ->willReturn($mount);
+
+ $result = $root->search('foo');
+ $cache->clear();
+ $this->assertEquals(1, count($result));
+ $this->assertEquals('/foo', $result[0]->getPath());
+ }
+
+ public function testSearchInStorageRoot(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->method('getUser')
+ ->willReturn($this->user);
+ $storage = $this->createMock(IStorage::class);
+ $storage->method('getId')->willReturn('test::1');
+ $cache = new Cache($storage);
+
+ $mount = $this->createMock(IMountPoint::class);
+ $mount->method('getStorage')
+ ->willReturn($storage);
+ $mount->method('getInternalPath')
+ ->willReturn('');
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
+
+
+ $root->method('getMountsIn')
+ ->with('/bar')
+ ->willReturn([]);
+
+ $root->method('getMount')
+ ->with('/bar')
+ ->willReturn($mount);
+
+ $node = new Folder($root, $view, '/bar');
+ $result = $node->search('qw');
+ $cache->clear();
+ $this->assertEquals(1, count($result));
+ $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
+ }
+
+ public function testSearchSubStorages(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+ $storage = $this->createMock(IStorage::class);
+ $storage->method('getId')->willReturn('test::1');
+ $cache = new Cache($storage);
+ $subStorage = $this->createMock(IStorage::class);
+ $subStorage->method('getId')->willReturn('test::2');
+ $subCache = new Cache($subStorage);
+ $subMount = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([Temporary::class, ''])->getMock();
+
+ $mount = $this->createMock(IMountPoint::class);
+ $mount->method('getStorage')
+ ->willReturn($storage);
+ $mount->method('getInternalPath')
+ ->willReturn('foo');
+
+ $subMount->method('getStorage')
+ ->willReturn($subStorage);
+
+ $subMount->method('getMountPoint')
+ ->willReturn('/bar/foo/bar/');
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $subStorage->method('getCache')
+ ->willReturn($subCache);
+ $subStorage->method('getOwner')
+ ->willReturn('owner');
+
+ $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
+
+ $subCache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $subCache->insert('asd', ['size' => 200, 'mtime' => 55, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $subCache->insert('asd/qwerty', ['size' => 200, 'mtime' => 55, 'mimetype' => 'text/plain']);
+
+
+ $root->method('getMountsIn')
+ ->with('/bar/foo')
+ ->willReturn([$subMount]);
+
+ $root->method('getMount')
+ ->with('/bar/foo')
+ ->willReturn($mount);
+
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $result = $node->search('qw');
+ $cache->clear();
+ $subCache->clear();
+ $this->assertEquals(2, count($result));
+ }
+
+ public function testIsSubNode(): void {
+ $rootFolderMock = $this->createMock(IRootFolder::class);
+ $file = new Node($rootFolderMock, $this->view, '/foo/bar');
+ $folder = new Folder($rootFolderMock, $this->view, '/foo');
+ $this->assertTrue($folder->isSubNode($file));
+ $this->assertFalse($folder->isSubNode($folder));
+
+ $file = new Node($rootFolderMock, $this->view, '/foobar');
+ $this->assertFalse($folder->isSubNode($file));
+ }
+
+ public function testGetById(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $storage = $this->createMock(Storage::class);
+ $mount = new MountPoint($storage, '/bar');
+ $storage->method('getId')->willReturn('');
+ $cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
+
+ $fileInfo = new CacheEntry(['path' => 'foo/qwerty', 'mimetype' => 'text/plain'], null);
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $this->userMountCache->expects($this->any())
+ ->method('getMountsForFileId')
+ ->with(1)
+ ->willReturn([new CachedMountInfo(
+ $this->user,
+ 1,
+ 0,
+ '/bar/',
+ 'test',
+ 1,
+ ''
+ )]);
+
+ $cache->method('get')
+ ->with(1)
+ ->willReturn($fileInfo);
+
+ $root->method('getMountsIn')
+ ->with('/bar/foo')
+ ->willReturn([]);
+
+ $manager->method('getMountsByMountProvider')
+ ->willReturn([$mount]);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $result = $node->getById(1);
+ $this->assertEquals(1, count($result));
+ $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
+ }
+
+ public function testGetByIdMountRoot(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $storage = $this->createMock(Storage::class);
+ $mount = new MountPoint($storage, '/bar');
+ $storage->method('getId')->willReturn('');
+ $cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
+
+ $fileInfo = new CacheEntry(['path' => '', 'mimetype' => 'text/plain'], null);
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $this->userMountCache->expects($this->any())
+ ->method('getMountsForFileId')
+ ->with(1)
+ ->willReturn([new CachedMountInfo(
+ $this->user,
+ 1,
+ 0,
+ '/bar/',
+ 'test',
+ 1,
+ ''
+ )]);
+
+ $cache->method('get')
+ ->with(1)
+ ->willReturn($fileInfo);
+
+ $manager->method('getMountsByMountProvider')
+ ->willReturn([$mount]);
+
+ $node = new Folder($root, $view, '/bar');
+ $result = $node->getById(1);
+ $this->assertEquals(1, count($result));
+ $this->assertEquals('/bar', $result[0]->getPath());
+ }
+
+ public function testGetByIdOutsideFolder(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $storage = $this->createMock(Storage::class);
+ $mount = new MountPoint($storage, '/bar');
+ $storage->method('getId')->willReturn('');
+ $cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
+
+ $fileInfo = new CacheEntry(['path' => 'foobar', 'mimetype' => 'text/plain'], null);
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $this->userMountCache->expects($this->any())
+ ->method('getMountsForFileId')
+ ->with(1)
+ ->willReturn([new CachedMountInfo(
+ $this->user,
+ 1,
+ 0,
+ '/bar/',
+ 'test',
+ 1,
+ ''
+ )]);
+
+ $cache->method('get')
+ ->with(1)
+ ->willReturn($fileInfo);
+
+ $manager->method('getMountsByMountProvider')
+ ->willReturn([$mount]);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $result = $node->getById(1);
+ $this->assertEquals(0, count($result));
+ }
+
+ public function testGetByIdMultipleStorages(): void {
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $storage = $this->createMock(Storage::class);
+ $mount1 = new MountPoint($storage, '/bar');
+ $mount2 = new MountPoint($storage, '/bar/foo/asd');
+ $storage->method('getId')->willReturn('');
+ $cache = $this->getMockBuilder(Cache::class)->setConstructorArgs([$storage])->getMock();
+
+ $fileInfo = new CacheEntry(['path' => 'foo/qwerty', 'mimetype' => 'text/plain'], null);
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $this->userMountCache->method('getMountsForFileId')
+ ->with(1)
+ ->willReturn([
+ new CachedMountInfo(
+ $this->user,
+ 1,
+ 0,
+ '/bar/',
+ 'test',
+ 1,
+ ''
+ ),
+ ]);
+
+ $cache->method('get')
+ ->with(1)
+ ->willReturn($fileInfo);
+
+ $manager->method('getMountsByMountProvider')
+ ->willReturn([$mount1, $mount2]);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $result = $node->getById(1);
+ $this->assertEquals(2, count($result));
+ $this->assertEquals('/bar/foo/qwerty', $result[0]->getPath());
+ $this->assertEquals('/bar/foo/asd/foo/qwerty', $result[1]->getPath());
+ }
+
+ public static function uniqueNameProvider(): array {
+ return [
+ // input, existing, expected
+ ['foo', [], 'foo'],
+ ['foo', ['foo'], 'foo (2)'],
+ ['foo', ['foo', 'foo (2)'], 'foo (3)'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('uniqueNameProvider')]
+ public function testGetUniqueName($name, $existingFiles, $expected): void {
+ $manager = $this->createMock(Manager::class);
+ $folderPath = '/bar/foo';
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getUser', 'getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+
+ $view->expects($this->any())
+ ->method('file_exists')
+ ->willReturnCallback(function ($path) use ($existingFiles, $folderPath) {
+ foreach ($existingFiles as $existing) {
+ if ($folderPath . '/' . $existing === $path) {
+ return true;
+ }
+ }
+ return false;
+ });
+
+ $node = new Folder($root, $view, $folderPath);
+ $this->assertEquals($expected, $node->getNonExistingName($name));
+ }
+
+ public function testRecent(): void {
+ $manager = $this->createMock(Manager::class);
+ $folderPath = '/bar/foo';
+ $view = $this->getRootViewMock();
+ /** @var \PHPUnit\Framework\MockObject\MockObject|\OC\Files\Node\Root $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getUser', 'getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ /** @var \PHPUnit\Framework\MockObject\MockObject|FileInfo $folderInfo */
+ $folderInfo = $this->getMockBuilder(FileInfo::class)
+ ->disableOriginalConstructor()->getMock();
+
+ $baseTime = time();
+ $storage = new Temporary();
+ $mount = new MountPoint($storage, '');
+
+ $folderInfo->expects($this->any())
+ ->method('getMountPoint')
+ ->willReturn($mount);
+ $root->method('getMount')
+ ->willReturn($mount);
+ $root->method('getMountsIn')
+ ->willReturn([]);
+
+ $cache = $storage->getCache();
+
+ $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('bar', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('bar/foo', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('bar/asd', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $id1 = $cache->put('bar/foo/inside.txt', [
+ 'storage_mtime' => $baseTime,
+ 'mtime' => $baseTime,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ 'permissions' => Constants::PERMISSION_ALL,
+ ]);
+ $id2 = $cache->put('bar/foo/old.txt', [
+ 'storage_mtime' => $baseTime - 100,
+ 'mtime' => $baseTime - 100,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ 'permissions' => Constants::PERMISSION_READ,
+ ]);
+ $cache->put('bar/asd/outside.txt', [
+ 'storage_mtime' => $baseTime,
+ 'mtime' => $baseTime,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ ]);
+ $id3 = $cache->put('bar/foo/older.txt', [
+ 'storage_mtime' => $baseTime - 600,
+ 'mtime' => $baseTime - 600,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ 'permissions' => Constants::PERMISSION_ALL,
+ ]);
+
+ $node = new Folder($root, $view, $folderPath, $folderInfo);
+
+
+ $nodes = $node->getRecent(5);
+ $ids = array_map(function (Node $node) {
+ return (int)$node->getId();
+ }, $nodes);
+ $this->assertEquals([$id1, $id2, $id3], $ids);
+ }
+
+ public function testRecentFolder(): void {
+ $manager = $this->createMock(Manager::class);
+ $folderPath = '/bar/foo';
+ $view = $this->getRootViewMock();
+ /** @var \PHPUnit\Framework\MockObject\MockObject|\OC\Files\Node\Root $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getUser', 'getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ /** @var \PHPUnit\Framework\MockObject\MockObject|FileInfo $folderInfo */
+ $folderInfo = $this->getMockBuilder(FileInfo::class)
+ ->disableOriginalConstructor()->getMock();
+
+ $baseTime = time();
+ $storage = new Temporary();
+ $mount = new MountPoint($storage, '');
+
+ $folderInfo->expects($this->any())
+ ->method('getMountPoint')
+ ->willReturn($mount);
+
+ $root->method('getMount')
+ ->willReturn($mount);
+ $root->method('getMountsIn')
+ ->willReturn([]);
+
+ $cache = $storage->getCache();
+
+ $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('bar', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('bar/foo', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $id1 = $cache->put('bar/foo/folder', [
+ 'storage_mtime' => $baseTime,
+ 'mtime' => $baseTime,
+ 'mimetype' => \OCP\Files\FileInfo::MIMETYPE_FOLDER,
+ 'size' => 3,
+ 'permissions' => 0,
+ ]);
+ $id2 = $cache->put('bar/foo/folder/bar.txt', [
+ 'storage_mtime' => $baseTime,
+ 'mtime' => $baseTime,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ 'parent' => $id1,
+ 'permissions' => Constants::PERMISSION_ALL,
+ ]);
+ $id3 = $cache->put('bar/foo/folder/asd.txt', [
+ 'storage_mtime' => $baseTime - 100,
+ 'mtime' => $baseTime - 100,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ 'parent' => $id1,
+ 'permissions' => Constants::PERMISSION_ALL,
+ ]);
+
+ $node = new Folder($root, $view, $folderPath, $folderInfo);
+
+
+ $nodes = $node->getRecent(5);
+ $ids = array_map(function (Node $node) {
+ return (int)$node->getId();
+ }, $nodes);
+ $this->assertEquals([$id2, $id3], $ids);
+ $this->assertEquals($baseTime, $nodes[0]->getMTime());
+ $this->assertEquals($baseTime - 100, $nodes[1]->getMTime());
+ }
+
+ public function testRecentJail(): void {
+ $manager = $this->createMock(Manager::class);
+ $folderPath = '/bar/foo';
+ $view = $this->getRootViewMock();
+ /** @var \PHPUnit\Framework\MockObject\MockObject|\OC\Files\Node\Root $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->onlyMethods(['getUser', 'getMountsIn', 'getMount'])
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ /** @var \PHPUnit\Framework\MockObject\MockObject|FileInfo $folderInfo */
+ $folderInfo = $this->getMockBuilder(FileInfo::class)
+ ->disableOriginalConstructor()->getMock();
+
+ $baseTime = time();
+ $storage = new Temporary();
+ $jail = new Jail([
+ 'storage' => $storage,
+ 'root' => 'folder',
+ ]);
+ $mount = new MountPoint($jail, '/bar/foo');
+
+ $folderInfo->expects($this->any())
+ ->method('getMountPoint')
+ ->willReturn($mount);
+ $root->method('getMount')
+ ->willReturn($mount);
+ $root->method('getMountsIn')
+ ->willReturn([]);
+
+ $cache = $storage->getCache();
+
+ $cache->insert('', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('folder', ['size' => 0, 'mtime' => 0, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $id1 = $cache->put('folder/inside.txt', [
+ 'storage_mtime' => $baseTime,
+ 'mtime' => $baseTime,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ 'permissions' => Constants::PERMISSION_ALL,
+ ]);
+
+ $cache->put('outside.txt', [
+ 'storage_mtime' => $baseTime - 100,
+ 'mtime' => $baseTime - 100,
+ 'mimetype' => 'text/plain',
+ 'size' => 3,
+ ]);
+
+ $node = new Folder($root, $view, $folderPath, $folderInfo);
+
+ $nodes = $node->getRecent(5);
+ $ids = array_map(function (Node $node) {
+ return (int)$node->getId();
+ }, $nodes);
+ $this->assertEquals([$id1], $ids);
+ }
+
+ public static function offsetLimitProvider(): array {
+ return [
+ [0, 10, ['/bar/foo/foo1', '/bar/foo/foo2', '/bar/foo/foo3', '/bar/foo/foo4', '/bar/foo/sub1/foo5', '/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7', '/bar/foo/sub2/foo8'], []],
+ [0, 5, ['/bar/foo/foo1', '/bar/foo/foo2', '/bar/foo/foo3', '/bar/foo/foo4', '/bar/foo/sub1/foo5'], []],
+ [0, 2, ['/bar/foo/foo1', '/bar/foo/foo2'], []],
+ [3, 2, ['/bar/foo/foo4', '/bar/foo/sub1/foo5'], []],
+ [3, 5, ['/bar/foo/foo4', '/bar/foo/sub1/foo5', '/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7', '/bar/foo/sub2/foo8'], []],
+ [5, 2, ['/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7'], []],
+ [6, 2, ['/bar/foo/sub2/foo7', '/bar/foo/sub2/foo8'], []],
+ [7, 2, ['/bar/foo/sub2/foo8'], []],
+ [10, 2, [], []],
+ [0, 5, ['/bar/foo/sub2/foo7', '/bar/foo/foo1', '/bar/foo/sub1/foo5', '/bar/foo/foo2', '/bar/foo/foo3'], [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')]],
+ [3, 2, ['/bar/foo/foo2', '/bar/foo/foo3'], [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')]],
+ [0, 5, ['/bar/foo/sub1/foo5', '/bar/foo/sub1/foo6', '/bar/foo/sub2/foo7', '/bar/foo/foo1', '/bar/foo/foo2'], [
+ new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'size'),
+ new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'mtime')
+ ]],
+ ];
+ }
+
+ /**
+ * @param int $offset
+ * @param int $limit
+ * @param string[] $expectedPaths
+ * @param ISearchOrder[] $ordering
+ * @throws NotFoundException
+ * @throws InvalidPathException
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('offsetLimitProvider')]
+ public function testSearchSubStoragesLimitOffset(int $offset, int $limit, array $expectedPaths, array $ordering): void {
+ if (!$ordering) {
+ $ordering = [new SearchOrder(ISearchOrder::DIRECTION_ASCENDING, 'fileid')];
+ }
+
+ $manager = $this->createMock(Manager::class);
+ $view = $this->getRootViewMock();
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$manager, $view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ $root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+ $storage = $this->createMock(IStorage::class);
+ $storage->method('getId')->willReturn('test::1');
+ $cache = new Cache($storage);
+ $subStorage1 = $this->createMock(IStorage::class);
+ $subStorage1->method('getId')->willReturn('test::2');
+ $subCache1 = new Cache($subStorage1);
+ $subMount1 = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([Temporary::class, ''])->getMock();
+ $subStorage2 = $this->createMock(IStorage::class);
+ $subStorage2->method('getId')->willReturn('test::3');
+ $subCache2 = new Cache($subStorage2);
+ $subMount2 = $this->getMockBuilder(MountPoint::class)->setConstructorArgs([Temporary::class, ''])->getMock();
+
+ $mount = $this->createMock(IMountPoint::class);
+ $mount->method('getStorage')
+ ->willReturn($storage);
+ $mount->method('getInternalPath')
+ ->willReturn('foo');
+
+ $subMount1->method('getStorage')
+ ->willReturn($subStorage1);
+
+ $subMount1->method('getMountPoint')
+ ->willReturn('/bar/foo/sub1/');
+
+ $storage->method('getCache')
+ ->willReturn($cache);
+ $storage->method('getOwner')
+ ->willReturn('owner');
+
+ $subStorage1->method('getCache')
+ ->willReturn($subCache1);
+ $subStorage1->method('getOwner')
+ ->willReturn('owner');
+
+ $subMount2->method('getStorage')
+ ->willReturn($subStorage2);
+
+ $subMount2->method('getMountPoint')
+ ->willReturn('/bar/foo/sub2/');
+
+ $subStorage2->method('getCache')
+ ->willReturn($subCache2);
+ $subStorage2->method('getOwner')
+ ->willReturn('owner');
+
+
+ $cache->insert('', ['size' => 0, 'mtime' => 10, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo', ['size' => 0, 'mtime' => 10, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $cache->insert('foo/foo1', ['size' => 200, 'mtime' => 10, 'mimetype' => 'text/plain']);
+ $cache->insert('foo/foo2', ['size' => 200, 'mtime' => 20, 'mimetype' => 'text/plain']);
+ $cache->insert('foo/foo3', ['size' => 200, 'mtime' => 30, 'mimetype' => 'text/plain']);
+ $cache->insert('foo/foo4', ['size' => 200, 'mtime' => 40, 'mimetype' => 'text/plain']);
+
+ $subCache1->insert('', ['size' => 0, 'mtime' => 10, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $subCache1->insert('foo5', ['size' => 300, 'mtime' => 15, 'mimetype' => 'text/plain']);
+ $subCache1->insert('foo6', ['size' => 300, 'mtime' => 50, 'mimetype' => 'text/plain']);
+
+ $subCache2->insert('', ['size' => 0, 'mtime' => 10, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);
+ $subCache2->insert('foo7', ['size' => 200, 'mtime' => 5, 'mimetype' => 'text/plain']);
+ $subCache2->insert('foo8', ['size' => 200, 'mtime' => 60, 'mimetype' => 'text/plain']);
+
+ $root->method('getMountsIn')
+ ->with('/bar/foo')
+ ->willReturn([$subMount1, $subMount2]);
+
+ $root->method('getMount')
+ ->with('/bar/foo')
+ ->willReturn($mount);
+
+ $node = new Folder($root, $view, '/bar/foo');
+ $comparison = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%foo%');
+ $operator = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [
+ $comparison,
+ new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_NOT, [new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', ICacheEntry::DIRECTORY_MIMETYPE)]),
+ ]);
+ $query = new SearchQuery($operator, $limit, $offset, $ordering);
+ $result = $node->search($query);
+ $cache->clear();
+ $subCache1->clear();
+ $subCache2->clear();
+ $ids = array_map(function (Node $info) {
+ return $info->getPath();
+ }, $result);
+ $this->assertEquals($expectedPaths, $ids);
+ }
+}
diff --git a/tests/lib/Files/Node/HookConnectorTest.php b/tests/lib/Files/Node/HookConnectorTest.php
new file mode 100644
index 00000000000..3f3957bab1d
--- /dev/null
+++ b/tests/lib/Files/Node/HookConnectorTest.php
@@ -0,0 +1,327 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Node;
+
+use OC\Files\Filesystem;
+use OC\Files\Node\HookConnector;
+use OC\Files\Node\Root;
+use OC\Files\Storage\Temporary;
+use OC\Files\View;
+use OC\Memcache\ArrayCache;
+use OCP\EventDispatcher\GenericEvent as APIGenericEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Config\IUserMountCache;
+use OCP\Files\Events\Node\AbstractNodeEvent;
+use OCP\Files\Events\Node\AbstractNodesEvent;
+use OCP\Files\Events\Node\BeforeNodeCopiedEvent;
+use OCP\Files\Events\Node\BeforeNodeCreatedEvent;
+use OCP\Files\Events\Node\BeforeNodeDeletedEvent;
+use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
+use OCP\Files\Events\Node\BeforeNodeTouchedEvent;
+use OCP\Files\Events\Node\BeforeNodeWrittenEvent;
+use OCP\Files\Events\Node\NodeCopiedEvent;
+use OCP\Files\Events\Node\NodeCreatedEvent;
+use OCP\Files\Events\Node\NodeDeletedEvent;
+use OCP\Files\Events\Node\NodeRenamedEvent;
+use OCP\Files\Events\Node\NodeTouchedEvent;
+use OCP\Files\Events\Node\NodeWrittenEvent;
+use OCP\Files\Node;
+use OCP\ICacheFactory;
+use OCP\IUserManager;
+use OCP\Server;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
+use Test\TestCase;
+use Test\Traits\MountProviderTrait;
+use Test\Traits\UserTrait;
+
+/**
+ * Class HookConnectorTest
+ *
+ * @group DB
+ *
+ * @package Test\Files\Node
+ */
+class HookConnectorTest extends TestCase {
+ use UserTrait;
+ use MountProviderTrait;
+
+ /** @var IEventDispatcher */
+ protected $eventDispatcher;
+
+ private LoggerInterface $logger;
+
+ /** @var View */
+ private $view;
+
+ /** @var Root */
+ private $root;
+
+ /** @var string */
+ private $userId;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->userId = $this->getUniqueID();
+ $this->createUser($this->userId, 'pass');
+ // this will setup the FS
+ $this->loginAsUser($this->userId);
+ $this->registerMount($this->userId, new Temporary(), '/' . $this->userId . '/files/');
+ $cacheFactory = $this->createMock(ICacheFactory::class);
+ $cacheFactory->method('createLocal')
+ ->willReturnCallback(function () {
+ return new ArrayCache();
+ });
+ $this->view = new View();
+ $this->root = new Root(
+ Filesystem::getMountManager(),
+ $this->view,
+ Server::get(IUserManager::class)->get($this->userId),
+ Server::get(IUserMountCache::class),
+ $this->createMock(LoggerInterface::class),
+ $this->createMock(IUserManager::class),
+ $this->createMock(IEventDispatcher::class),
+ $cacheFactory,
+ );
+ $this->eventDispatcher = Server::get(IEventDispatcher::class);
+ $this->logger = Server::get(LoggerInterface::class);
+ }
+
+ protected function tearDown(): void {
+ parent::tearDown();
+ \OC_Hook::clear('OC_Filesystem');
+ \OC_Util::tearDownFS();
+ }
+
+ public static function viewToNodeProvider(): array {
+ return [
+ [function (): void {
+ Filesystem::file_put_contents('test.txt', 'asd');
+ }, 'preWrite', '\OCP\Files::preWrite', BeforeNodeWrittenEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('test.txt', 'asd');
+ }, 'postWrite', '\OCP\Files::postWrite', NodeWrittenEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('test.txt', 'asd');
+ }, 'preCreate', '\OCP\Files::preCreate', BeforeNodeCreatedEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('test.txt', 'asd');
+ }, 'postCreate', '\OCP\Files::postCreate', NodeCreatedEvent::class],
+ [function (): void {
+ Filesystem::mkdir('test.txt');
+ }, 'preCreate', '\OCP\Files::preCreate', BeforeNodeCreatedEvent::class],
+ [function (): void {
+ Filesystem::mkdir('test.txt');
+ }, 'postCreate', '\OCP\Files::postCreate', NodeCreatedEvent::class],
+ [function (): void {
+ Filesystem::touch('test.txt');
+ }, 'preTouch', '\OCP\Files::preTouch', BeforeNodeTouchedEvent::class],
+ [function (): void {
+ Filesystem::touch('test.txt');
+ }, 'postTouch', '\OCP\Files::postTouch', NodeTouchedEvent::class],
+ [function (): void {
+ Filesystem::touch('test.txt');
+ }, 'preCreate', '\OCP\Files::preCreate', BeforeNodeCreatedEvent::class],
+ [function (): void {
+ Filesystem::touch('test.txt');
+ }, 'postCreate', '\OCP\Files::postCreate', NodeCreatedEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('test.txt', 'asd');
+ Filesystem::unlink('test.txt');
+ }, 'preDelete', '\OCP\Files::preDelete', BeforeNodeDeletedEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('test.txt', 'asd');
+ Filesystem::unlink('test.txt');
+ }, 'postDelete', '\OCP\Files::postDelete', NodeDeletedEvent::class],
+ [function (): void {
+ Filesystem::mkdir('test.txt');
+ Filesystem::rmdir('test.txt');
+ }, 'preDelete', '\OCP\Files::preDelete', BeforeNodeDeletedEvent::class],
+ [function (): void {
+ Filesystem::mkdir('test.txt');
+ Filesystem::rmdir('test.txt');
+ }, 'postDelete', '\OCP\Files::postDelete', NodeDeletedEvent::class],
+ ];
+ }
+
+ /**
+ * @param callable $operation
+ * @param string $expectedHook
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('viewToNodeProvider')]
+ public function testViewToNode(callable $operation, $expectedHook, $expectedLegacyEvent, $expectedEvent): void {
+ $connector = new HookConnector($this->root, $this->view, $this->eventDispatcher, $this->logger);
+ $connector->viewToNode();
+ $hookCalled = false;
+ /** @var Node $hookNode */
+ $hookNode = null;
+
+ $this->root->listen('\OC\Files', $expectedHook, function ($node) use (&$hookNode, &$hookCalled): void {
+ $hookCalled = true;
+ $hookNode = $node;
+ });
+
+ $dispatcherCalled = false;
+ /** @var Node $dispatcherNode */
+ $dispatcherNode = null;
+ $this->eventDispatcher->addListener($expectedLegacyEvent, function ($event) use (&$dispatcherCalled, &$dispatcherNode): void {
+ /** @var GenericEvent|APIGenericEvent $event */
+ $dispatcherCalled = true;
+ $dispatcherNode = $event->getSubject();
+ });
+
+ $newDispatcherCalled = false;
+ $newDispatcherNode = null;
+ $this->eventDispatcher->addListener($expectedEvent, function ($event) use ($expectedEvent, &$newDispatcherCalled, &$newDispatcherNode): void {
+ if ($event instanceof $expectedEvent) {
+ /** @var AbstractNodeEvent $event */
+ $newDispatcherCalled = true;
+ $newDispatcherNode = $event->getNode();
+ }
+ });
+
+ $operation();
+
+ $this->assertTrue($hookCalled);
+ $this->assertEquals('/' . $this->userId . '/files/test.txt', $hookNode->getPath());
+
+ $this->assertTrue($dispatcherCalled);
+ $this->assertEquals('/' . $this->userId . '/files/test.txt', $dispatcherNode->getPath());
+
+ $this->assertTrue($newDispatcherCalled);
+ $this->assertEquals('/' . $this->userId . '/files/test.txt', $newDispatcherNode->getPath());
+ }
+
+ public static function viewToNodeProviderCopyRename(): array {
+ return [
+ [function (): void {
+ Filesystem::file_put_contents('source', 'asd');
+ Filesystem::rename('source', 'target');
+ }, 'preRename', '\OCP\Files::preRename', BeforeNodeRenamedEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('source', 'asd');
+ Filesystem::rename('source', 'target');
+ }, 'postRename', '\OCP\Files::postRename', NodeRenamedEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('source', 'asd');
+ Filesystem::copy('source', 'target');
+ }, 'preCopy', '\OCP\Files::preCopy', BeforeNodeCopiedEvent::class],
+ [function (): void {
+ Filesystem::file_put_contents('source', 'asd');
+ Filesystem::copy('source', 'target');
+ }, 'postCopy', '\OCP\Files::postCopy', NodeCopiedEvent::class],
+ ];
+ }
+
+ /**
+ * @param callable $operation
+ * @param string $expectedHook
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('viewToNodeProviderCopyRename')]
+ public function testViewToNodeCopyRename(callable $operation, $expectedHook, $expectedLegacyEvent, $expectedEvent): void {
+ $connector = new HookConnector($this->root, $this->view, $this->eventDispatcher, $this->logger);
+ $connector->viewToNode();
+ $hookCalled = false;
+ /** @var Node $hookSourceNode */
+ $hookSourceNode = null;
+ /** @var Node $hookTargetNode */
+ $hookTargetNode = null;
+
+ $this->root->listen('\OC\Files', $expectedHook, function ($sourceNode, $targetNode) use (&$hookCalled, &$hookSourceNode, &$hookTargetNode): void {
+ $hookCalled = true;
+ $hookSourceNode = $sourceNode;
+ $hookTargetNode = $targetNode;
+ });
+
+ $dispatcherCalled = false;
+ /** @var Node $dispatcherSourceNode */
+ $dispatcherSourceNode = null;
+ /** @var Node $dispatcherTargetNode */
+ $dispatcherTargetNode = null;
+ $this->eventDispatcher->addListener($expectedLegacyEvent, function ($event) use (&$dispatcherSourceNode, &$dispatcherTargetNode, &$dispatcherCalled): void {
+ /** @var GenericEvent|APIGenericEvent $event */
+ $dispatcherCalled = true;
+ [$dispatcherSourceNode, $dispatcherTargetNode] = $event->getSubject();
+ });
+
+ $newDispatcherCalled = false;
+ /** @var Node $dispatcherSourceNode */
+ $newDispatcherSourceNode = null;
+ /** @var Node $dispatcherTargetNode */
+ $newDispatcherTargetNode = null;
+ $this->eventDispatcher->addListener($expectedEvent, function ($event) use ($expectedEvent, &$newDispatcherSourceNode, &$newDispatcherTargetNode, &$newDispatcherCalled): void {
+ if ($event instanceof $expectedEvent) {
+ /** @var AbstractNodesEvent$event */
+ $newDispatcherCalled = true;
+ $newDispatcherSourceNode = $event->getSource();
+ $newDispatcherTargetNode = $event->getTarget();
+ }
+ });
+
+ $operation();
+
+ $this->assertTrue($hookCalled);
+ $this->assertEquals('/' . $this->userId . '/files/source', $hookSourceNode->getPath());
+ $this->assertEquals('/' . $this->userId . '/files/target', $hookTargetNode->getPath());
+
+ $this->assertTrue($dispatcherCalled);
+ $this->assertEquals('/' . $this->userId . '/files/source', $dispatcherSourceNode->getPath());
+ $this->assertEquals('/' . $this->userId . '/files/target', $dispatcherTargetNode->getPath());
+
+ $this->assertTrue($newDispatcherCalled);
+ $this->assertEquals('/' . $this->userId . '/files/source', $newDispatcherSourceNode->getPath());
+ $this->assertEquals('/' . $this->userId . '/files/target', $newDispatcherTargetNode->getPath());
+ }
+
+ public function testPostDeleteMeta(): void {
+ $connector = new HookConnector($this->root, $this->view, $this->eventDispatcher, $this->logger);
+ $connector->viewToNode();
+ $hookCalled = false;
+ /** @var Node $hookNode */
+ $hookNode = null;
+
+ $this->root->listen('\OC\Files', 'postDelete', function ($node) use (&$hookNode, &$hookCalled): void {
+ $hookCalled = true;
+ $hookNode = $node;
+ });
+
+ $dispatcherCalled = false;
+ /** @var Node $dispatcherNode */
+ $dispatcherNode = null;
+ $this->eventDispatcher->addListener('\OCP\Files::postDelete', function ($event) use (&$dispatcherCalled, &$dispatcherNode): void {
+ /** @var GenericEvent|APIGenericEvent $event */
+ $dispatcherCalled = true;
+ $dispatcherNode = $event->getSubject();
+ });
+
+ $newDispatcherCalled = false;
+ /** @var Node $dispatcherNode */
+ $newDispatcherNode = null;
+ $this->eventDispatcher->addListener(NodeDeletedEvent::class, function ($event) use (&$newDispatcherCalled, &$newDispatcherNode): void {
+ if ($event instanceof NodeDeletedEvent) {
+ /** @var AbstractNodeEvent $event */
+ $newDispatcherCalled = true;
+ $newDispatcherNode = $event->getNode();
+ }
+ });
+
+ Filesystem::file_put_contents('test.txt', 'asd');
+ $info = Filesystem::getFileInfo('test.txt');
+ Filesystem::unlink('test.txt');
+
+ $this->assertTrue($hookCalled);
+ $this->assertEquals($hookNode->getId(), $info->getId());
+
+ $this->assertTrue($dispatcherCalled);
+ $this->assertEquals($dispatcherNode->getId(), $info->getId());
+
+ $this->assertTrue($newDispatcherCalled);
+ $this->assertEquals($newDispatcherNode->getId(), $info->getId());
+ }
+}
diff --git a/tests/lib/Files/Node/IntegrationTest.php b/tests/lib/Files/Node/IntegrationTest.php
new file mode 100644
index 00000000000..f059afa1625
--- /dev/null
+++ b/tests/lib/Files/Node/IntegrationTest.php
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Node;
+
+use OC\Files\Node\Root;
+use OC\Files\Storage\Storage;
+use OC\Files\Storage\Temporary;
+use OC\Files\View;
+use OC\Memcache\ArrayCache;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Config\IUserMountCache;
+use OCP\Files\Mount\IMountManager;
+use OCP\ICacheFactory;
+use OCP\IUserManager;
+use OCP\Server;
+use Psr\Log\LoggerInterface;
+use Test\Traits\UserTrait;
+
+/**
+ * Class IntegrationTest
+ *
+ * @group DB
+ *
+ * @package Test\Files\Node
+ */
+class IntegrationTest extends \Test\TestCase {
+ use UserTrait;
+
+ /**
+ * @var \OC\Files\Node\Root $root
+ */
+ private $root;
+
+ /**
+ * @var Storage[]
+ */
+ private $storages;
+
+ /**
+ * @var View $view
+ */
+ private $view;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $manager = Server::get(IMountManager::class);
+
+ \OC_Hook::clear('OC_Filesystem');
+
+ $user = $this->createUser($this->getUniqueID('user'), '');
+ $this->loginAsUser($user->getUID());
+ $cacheFactory = $this->createMock(ICacheFactory::class);
+ $cacheFactory->method('createLocal')
+ ->willReturnCallback(function () {
+ return new ArrayCache();
+ });
+
+ $this->view = new View();
+ $this->root = new Root(
+ $manager,
+ $this->view,
+ $user,
+ Server::get(IUserMountCache::class),
+ $this->createMock(LoggerInterface::class),
+ $this->createMock(IUserManager::class),
+ $this->createMock(IEventDispatcher::class),
+ $cacheFactory,
+ );
+ $storage = new Temporary([]);
+ $subStorage = new Temporary([]);
+ $this->storages[] = $storage;
+ $this->storages[] = $subStorage;
+ $this->root->mount($storage, '/');
+ $this->root->mount($subStorage, '/substorage/');
+ $manager->removeMount('/' . $user->getUID());
+ }
+
+ protected function tearDown(): void {
+ foreach ($this->storages as $storage) {
+ $storage->getCache()->clear();
+ }
+
+ $this->logout();
+ parent::tearDown();
+ }
+
+ public function testBasicFile(): void {
+ $file = $this->root->newFile('/foo.txt');
+ $this->assertCount(2, $this->root->getDirectoryListing());
+ $this->assertTrue($this->root->nodeExists('/foo.txt'));
+ $id = $file->getId();
+ $this->assertInstanceOf('\OC\Files\Node\File', $file);
+ $file->putContent('qwerty');
+ $this->assertEquals('text/plain', $file->getMimeType());
+ $this->assertEquals('qwerty', $file->getContent());
+ $this->assertFalse($this->root->nodeExists('/bar.txt'));
+ $target = $file->move('/bar.txt');
+ $this->assertEquals($id, $target->getId());
+ $this->assertEquals($id, $file->getId());
+ $this->assertFalse($this->root->nodeExists('/foo.txt'));
+ $this->assertTrue($this->root->nodeExists('/bar.txt'));
+ $this->assertEquals('bar.txt', $file->getName());
+ $this->assertEquals('bar.txt', $file->getInternalPath());
+
+ $file->move('/substorage/bar.txt');
+ $this->assertEquals($id, $file->getId());
+ $this->assertEquals('qwerty', $file->getContent());
+ }
+
+ public function testBasicFolder(): void {
+ $folder = $this->root->newFolder('/foo');
+ $this->assertTrue($this->root->nodeExists('/foo'));
+ $file = $folder->newFile('/bar');
+ $this->assertTrue($this->root->nodeExists('/foo/bar'));
+ $file->putContent('qwerty');
+
+ $listing = $folder->getDirectoryListing();
+ $this->assertEquals(1, count($listing));
+ $this->assertEquals($file->getId(), $listing[0]->getId());
+ $this->assertEquals($file->getStorage(), $listing[0]->getStorage());
+
+
+ $rootListing = $this->root->getDirectoryListing();
+ $this->assertEquals(2, count($rootListing));
+
+ $folder->move('/asd');
+ /**
+ * @var \OC\Files\Node\File $file
+ */
+ $file = $folder->get('/bar');
+ $this->assertInstanceOf('\OC\Files\Node\File', $file);
+ $this->assertFalse($this->root->nodeExists('/foo/bar'));
+ $this->assertTrue($this->root->nodeExists('/asd/bar'));
+ $this->assertEquals('qwerty', $file->getContent());
+ $folder->move('/substorage/foo');
+ /**
+ * @var \OC\Files\Node\File $file
+ */
+ $file = $folder->get('/bar');
+ $this->assertInstanceOf('\OC\Files\Node\File', $file);
+ $this->assertTrue($this->root->nodeExists('/substorage/foo/bar'));
+ $this->assertEquals('qwerty', $file->getContent());
+ }
+}
diff --git a/tests/lib/Files/Node/NodeTestCase.php b/tests/lib/Files/Node/NodeTestCase.php
new file mode 100644
index 00000000000..4aecd0fef11
--- /dev/null
+++ b/tests/lib/Files/Node/NodeTestCase.php
@@ -0,0 +1,793 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Node;
+
+use OC\Files\FileInfo;
+use OC\Files\Mount\Manager;
+use OC\Files\Node\File;
+use OC\Files\Node\Folder;
+use OC\Files\Node\Root;
+use OC\Files\Storage\Storage;
+use OC\Files\View;
+use OC\Memcache\ArrayCache;
+use OC\User\User;
+use OCP\Constants;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Config\IUserMountCache;
+use OCP\Files\InvalidPathException;
+use OCP\Files\IRootFolder;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\Files\Storage\IStorage;
+use OCP\ICacheFactory;
+use OCP\IUser;
+use OCP\IUserManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class NodeTest
+ *
+ * @package Test\Files\Node
+ */
+abstract class NodeTestCase extends \Test\TestCase {
+ /** @var User */
+ protected $user;
+ /** @var \OC\Files\Mount\Manager */
+ protected $manager;
+ /** @var View|\PHPUnit\Framework\MockObject\MockObject */
+ protected $view;
+ /** @var \OC\Files\Node\Root|\PHPUnit\Framework\MockObject\MockObject */
+ protected $root;
+ /** @var IUserMountCache|\PHPUnit\Framework\MockObject\MockObject */
+ protected $userMountCache;
+ /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
+ protected $logger;
+ /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
+ protected $userManager;
+ /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
+ protected $eventDispatcher;
+ /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */
+ protected $cacheFactory;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->user = $this->createMock(IUser::class);
+ $this->manager = $this->getMockBuilder(Manager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->view = $this->getMockBuilder(View::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->view->expects($this->any())
+ ->method('getRoot')
+ ->willReturn('');
+ $this->userMountCache = $this->getMockBuilder('\OCP\Files\Config\IUserMountCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+ $this->cacheFactory->method('createLocal')
+ ->willReturnCallback(function () {
+ return new ArrayCache();
+ });
+ $this->root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$this->manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->getMock();
+ }
+
+ /**
+ * @return View|\PHPUnit\Framework\MockObject\MockObject $view
+ */
+ protected function getRootViewMock() {
+ $view = $this->createMock(View::class);
+ $view->expects($this->any())
+ ->method('getRoot')
+ ->willReturn('');
+ return $view;
+ }
+
+ /**
+ * @param IRootFolder $root
+ * @param View $view
+ * @param string $path
+ * @return Node
+ */
+ abstract protected function createTestNode($root, $view, $path, array $data = [], $internalPath = '', $storage = null);
+
+ /**
+ * @return string
+ */
+ abstract protected function getNodeClass();
+
+ /**
+ * @return string
+ */
+ abstract protected function getNonExistingNodeClass();
+
+ /**
+ * @return string
+ */
+ abstract protected function getViewDeleteMethod();
+
+ protected function getMockStorage() {
+ $storage = $this->getMockBuilder(IStorage::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $storage->expects($this->any())
+ ->method('getId')
+ ->willReturn('home::someuser');
+ return $storage;
+ }
+
+ protected function getFileInfo($data, $internalPath = '', $storage = null) {
+ $mount = $this->createMock(IMountPoint::class);
+ $mount->method('getStorage')
+ ->willReturn($storage);
+ return new FileInfo('', $this->getMockStorage(), $internalPath, $data, $mount);
+ }
+
+ public function testDelete(): void {
+ $this->root->expects($this->exactly(2))
+ ->method('emit')
+ ->willReturn(true);
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $this->view->expects($this->once())
+ ->method($this->getViewDeleteMethod())
+ ->with('/bar/foo')
+ ->willReturn(true);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $node->delete();
+ }
+
+ public function testDeleteHooks(): void {
+ $test = $this;
+ $hooksRun = 0;
+ /**
+ * @param \OC\Files\Node\File $node
+ */
+ $preListener = function ($node) use (&$test, &$hooksRun): void {
+ $test->assertInstanceOf($this->getNodeClass(), $node);
+ $test->assertEquals('foo', $node->getInternalPath());
+ $test->assertEquals('/bar/foo', $node->getPath());
+ $test->assertEquals(1, $node->getId());
+ $hooksRun++;
+ };
+
+ /**
+ * @param \OC\Files\Node\File $node
+ */
+ $postListener = function ($node) use (&$test, &$hooksRun): void {
+ $test->assertInstanceOf($this->getNonExistingNodeClass(), $node);
+ $test->assertEquals('foo', $node->getInternalPath());
+ $test->assertEquals('/bar/foo', $node->getPath());
+ $test->assertEquals(1, $node->getId());
+ $test->assertEquals('text/plain', $node->getMimeType());
+ $hooksRun++;
+ };
+
+ $root = new Root(
+ $this->manager,
+ $this->view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+
+ $root->listen('\OC\Files', 'preDelete', $preListener);
+ $root->listen('\OC\Files', 'postDelete', $postListener);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL, 'fileid' => 1, 'mimetype' => 'text/plain'], 'foo'));
+
+ $this->view->expects($this->once())
+ ->method($this->getViewDeleteMethod())
+ ->with('/bar/foo')
+ ->willReturn(true);
+
+ $node = $this->createTestNode($root, $this->view, '/bar/foo');
+ $node->delete();
+ $this->assertEquals(2, $hooksRun);
+ }
+
+
+ public function testDeleteNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $node->delete();
+ }
+
+
+ public function testStat(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $stat = [
+ 'fileid' => 1,
+ 'size' => 100,
+ 'etag' => 'qwerty',
+ 'mtime' => 50,
+ 'permissions' => 0
+ ];
+
+ $this->view->expects($this->once())
+ ->method('stat')
+ ->with('/bar/foo')
+ ->willReturn($stat);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals($stat, $node->stat());
+ }
+
+ public function testGetId(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $stat = $this->getFileInfo([
+ 'fileid' => 1,
+ 'size' => 100,
+ 'etag' => 'qwerty',
+ 'mtime' => 50
+ ]);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($stat);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals(1, $node->getId());
+ }
+
+ public function testGetSize(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+
+ $stat = $this->getFileInfo([
+ 'fileid' => 1,
+ 'size' => 100,
+ 'etag' => 'qwerty',
+ 'mtime' => 50
+ ]);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($stat);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals(100, $node->getSize());
+ }
+
+ public function testGetEtag(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $stat = $this->getFileInfo([
+ 'fileid' => 1,
+ 'size' => 100,
+ 'etag' => 'qwerty',
+ 'mtime' => 50
+ ]);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($stat);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals('qwerty', $node->getEtag());
+ }
+
+ public function testGetMTime(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $stat = $this->getFileInfo([
+ 'fileid' => 1,
+ 'size' => 100,
+ 'etag' => 'qwerty',
+ 'mtime' => 50
+ ]);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($stat);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals(50, $node->getMTime());
+ }
+
+ public function testGetStorage(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+ /**
+ * @var Storage|\PHPUnit\Framework\MockObject\MockObject $storage
+ */
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo', [], 'foo', $storage);
+ $this->assertEquals($storage, $node->getStorage());
+ }
+
+ public function testGetPath(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals('/bar/foo', $node->getPath());
+ }
+
+ public function testGetInternalPath(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+ /**
+ * @var Storage|\PHPUnit\Framework\MockObject\MockObject $storage
+ */
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo([], 'foo'));
+
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals('foo', $node->getInternalPath());
+ }
+
+ public function testGetName(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $this->assertEquals('foo', $node->getName());
+ }
+
+ public function testTouchSetMTime(): void {
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $this->view->expects($this->once())
+ ->method('touch')
+ ->with('/bar/foo', 100)
+ ->willReturn(true);
+
+ $this->view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $node->touch(100);
+ $this->assertEquals(100, $node->getMTime());
+ }
+
+ public function testTouchHooks(): void {
+ $test = $this;
+ $hooksRun = 0;
+ /**
+ * @param \OC\Files\Node\File $node
+ */
+ $preListener = function ($node) use (&$test, &$hooksRun): void {
+ $test->assertEquals('foo', $node->getInternalPath());
+ $test->assertEquals('/bar/foo', $node->getPath());
+ $hooksRun++;
+ };
+
+ /**
+ * @param \OC\Files\Node\File $node
+ */
+ $postListener = function ($node) use (&$test, &$hooksRun): void {
+ $test->assertEquals('foo', $node->getInternalPath());
+ $test->assertEquals('/bar/foo', $node->getPath());
+ $hooksRun++;
+ };
+
+ $root = new Root(
+ $this->manager,
+ $this->view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+ $root->listen('\OC\Files', 'preTouch', $preListener);
+ $root->listen('\OC\Files', 'postTouch', $postListener);
+
+ $this->view->expects($this->once())
+ ->method('touch')
+ ->with('/bar/foo', 100)
+ ->willReturn(true);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL], 'foo'));
+
+ $node = $this->createTestNode($root, $this->view, '/bar/foo');
+ $node->touch(100);
+ $this->assertEquals(2, $hooksRun);
+ }
+
+
+ public function testTouchNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $this->root->expects($this->any())
+ ->method('getUser')
+ ->willReturn($this->user);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $node->touch(100);
+ }
+
+
+ public function testInvalidPath(): void {
+ $this->expectException(InvalidPathException::class);
+
+ $node = $this->createTestNode($this->root, $this->view, '/../foo');
+ $node->getFileInfo();
+ }
+
+ public function testCopySameStorage(): void {
+ $this->view->expects($this->any())
+ ->method('copy')
+ ->with('/bar/foo', '/bar/asd')
+ ->willReturn(true);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL, 'fileid' => 3]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new Folder($this->root, $this->view, '/bar');
+ $newNode = $this->createTestNode($this->root, $this->view, '/bar/asd');
+
+ $this->root->method('get')
+ ->willReturnMap([
+ ['/bar/asd', $newNode],
+ ['/bar', $parentNode]
+ ]);
+
+ $target = $node->copy('/bar/asd');
+ $this->assertInstanceOf($this->getNodeClass(), $target);
+ $this->assertEquals(3, $target->getId());
+ }
+
+
+ public function testCopyNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ /**
+ * @var Storage|\PHPUnit\Framework\MockObject\MockObject $storage
+ */
+ $storage = $this->createMock('\OC\Files\Storage\Storage');
+
+ $this->root->expects($this->never())
+ ->method('getMount');
+
+ $storage->expects($this->never())
+ ->method('copy');
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ, 'fileid' => 3]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new Folder($this->root, $this->view, '/bar');
+
+ $this->root->expects($this->once())
+ ->method('get')
+ ->willReturnMap([
+ ['/bar', $parentNode]
+ ]);
+
+ $node->copy('/bar/asd');
+ }
+
+
+ public function testCopyNoParent(): void {
+ $this->expectException(NotFoundException::class);
+
+ $this->view->expects($this->never())
+ ->method('copy');
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+
+ $this->root->expects($this->once())
+ ->method('get')
+ ->with('/bar/asd')
+ ->willThrowException(new NotFoundException());
+
+ $node->copy('/bar/asd/foo');
+ }
+
+
+ public function testCopyParentIsFile(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $this->view->expects($this->never())
+ ->method('copy');
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new File($this->root, $this->view, '/bar');
+
+ $this->root->expects($this->once())
+ ->method('get')
+ ->willReturnMap([
+ ['/bar', $parentNode]
+ ]);
+
+ $node->copy('/bar/asd');
+ }
+
+ public function testMoveSameStorage(): void {
+ $this->view->expects($this->any())
+ ->method('rename')
+ ->with('/bar/foo', '/bar/asd')
+ ->willReturn(true);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL, 'fileid' => 1]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new Folder($this->root, $this->view, '/bar');
+
+ $this->root->expects($this->any())
+ ->method('get')
+ ->willReturnMap([['/bar', $parentNode], ['/bar/asd', $node]]);
+
+ $target = $node->move('/bar/asd');
+ $this->assertInstanceOf($this->getNodeClass(), $target);
+ $this->assertEquals(1, $target->getId());
+ $this->assertEquals('/bar/asd', $node->getPath());
+ }
+
+ public static function moveOrCopyProvider(): array {
+ return [
+ ['move', 'rename', 'preRename', 'postRename'],
+ ['copy', 'copy', 'preCopy', 'postCopy'],
+ ];
+ }
+
+ /**
+ * @param string $operationMethod
+ * @param string $viewMethod
+ * @param string $preHookName
+ * @param string $postHookName
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('moveOrCopyProvider')]
+ public function testMoveCopyHooks($operationMethod, $viewMethod, $preHookName, $postHookName): void {
+ /** @var IRootFolder|\PHPUnit\Framework\MockObject\MockObject $root */
+ $root = $this->getMockBuilder(Root::class)
+ ->setConstructorArgs([$this->manager, $this->view, $this->user, $this->userMountCache, $this->logger, $this->userManager, $this->eventDispatcher, $this->cacheFactory])
+ ->onlyMethods(['get'])
+ ->getMock();
+
+ $this->view->expects($this->any())
+ ->method($viewMethod)
+ ->with('/bar/foo', '/bar/asd')
+ ->willReturn(true);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL, 'fileid' => 1]));
+
+ /**
+ * @var \OC\Files\Node\File|\PHPUnit\Framework\MockObject\MockObject $node
+ */
+ $node = $this->createTestNode($root, $this->view, '/bar/foo');
+ $parentNode = new Folder($root, $this->view, '/bar');
+ $targetTestNode = $this->createTestNode($root, $this->view, '/bar/asd');
+
+ $root->expects($this->any())
+ ->method('get')
+ ->willReturnMap([['/bar', $parentNode], ['/bar/asd', $targetTestNode]]);
+
+ $hooksRun = 0;
+
+ $preListener = function (Node $sourceNode, Node $targetNode) use (&$hooksRun, $node): void {
+ $this->assertSame($node, $sourceNode);
+ $this->assertInstanceOf($this->getNodeClass(), $sourceNode);
+ $this->assertInstanceOf($this->getNonExistingNodeClass(), $targetNode);
+ $this->assertEquals('/bar/asd', $targetNode->getPath());
+ $hooksRun++;
+ };
+
+ $postListener = function (Node $sourceNode, Node $targetNode) use (&$hooksRun, $node, $targetTestNode): void {
+ $this->assertSame($node, $sourceNode);
+ $this->assertNotSame($node, $targetNode);
+ $this->assertSame($targetTestNode, $targetNode);
+ $this->assertInstanceOf($this->getNodeClass(), $sourceNode);
+ $this->assertInstanceOf($this->getNodeClass(), $targetNode);
+ $hooksRun++;
+ };
+
+ $preWriteListener = function (Node $targetNode) use (&$hooksRun): void {
+ $this->assertInstanceOf($this->getNonExistingNodeClass(), $targetNode);
+ $this->assertEquals('/bar/asd', $targetNode->getPath());
+ $hooksRun++;
+ };
+
+ $postWriteListener = function (Node $targetNode) use (&$hooksRun, $targetTestNode): void {
+ $this->assertSame($targetTestNode, $targetNode);
+ $hooksRun++;
+ };
+
+ $root->listen('\OC\Files', $preHookName, $preListener);
+ $root->listen('\OC\Files', 'preWrite', $preWriteListener);
+ $root->listen('\OC\Files', $postHookName, $postListener);
+ $root->listen('\OC\Files', 'postWrite', $postWriteListener);
+
+ $node->$operationMethod('/bar/asd');
+
+ $this->assertEquals(4, $hooksRun);
+ }
+
+
+ public function testMoveNotPermitted(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_READ]));
+
+ $this->view->expects($this->never())
+ ->method('rename');
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new Folder($this->root, $this->view, '/bar');
+
+ $this->root->expects($this->once())
+ ->method('get')
+ ->with('/bar')
+ ->willReturn($parentNode);
+
+ $node->move('/bar/asd');
+ }
+
+
+ public function testMoveNoParent(): void {
+ $this->expectException(NotFoundException::class);
+
+ /**
+ * @var Storage|\PHPUnit\Framework\MockObject\MockObject $storage
+ */
+ $storage = $this->createMock('\OC\Files\Storage\Storage');
+
+ $storage->expects($this->never())
+ ->method('rename');
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+
+ $this->root->expects($this->once())
+ ->method('get')
+ ->with('/bar')
+ ->willThrowException(new NotFoundException());
+
+ $node->move('/bar/asd');
+ }
+
+
+ public function testMoveParentIsFile(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $this->view->expects($this->never())
+ ->method('rename');
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new File($this->root, $this->view, '/bar');
+
+ $this->root->expects($this->once())
+ ->method('get')
+ ->with('/bar')
+ ->willReturn($parentNode);
+
+ $node->move('/bar/asd');
+ }
+
+
+ public function testMoveFailed(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $this->view->expects($this->any())
+ ->method('rename')
+ ->with('/bar/foo', '/bar/asd')
+ ->willReturn(false);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL, 'fileid' => 1]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new Folder($this->root, $this->view, '/bar');
+
+ $this->root->expects($this->any())
+ ->method('get')
+ ->willReturnMap([['/bar', $parentNode], ['/bar/asd', $node]]);
+
+ $node->move('/bar/asd');
+ }
+
+
+ public function testCopyFailed(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $this->view->expects($this->any())
+ ->method('copy')
+ ->with('/bar/foo', '/bar/asd')
+ ->willReturn(false);
+
+ $this->view->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($this->getFileInfo(['permissions' => Constants::PERMISSION_ALL, 'fileid' => 1]));
+
+ $node = $this->createTestNode($this->root, $this->view, '/bar/foo');
+ $parentNode = new Folder($this->root, $this->view, '/bar');
+
+ $this->root->expects($this->any())
+ ->method('get')
+ ->willReturnMap([['/bar', $parentNode], ['/bar/asd', $node]]);
+
+ $node->copy('/bar/asd');
+ }
+}
diff --git a/tests/lib/Files/Node/RootTest.php b/tests/lib/Files/Node/RootTest.php
new file mode 100644
index 00000000000..d90e6a2cc6e
--- /dev/null
+++ b/tests/lib/Files/Node/RootTest.php
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Files\Node;
+
+use OC\Files\FileInfo;
+use OC\Files\Mount\Manager;
+use OC\Files\Node\Folder;
+use OC\Files\Node\Root;
+use OC\Files\Storage\Storage;
+use OC\Files\View;
+use OC\Memcache\ArrayCache;
+use OC\User\NoUserException;
+use OC\User\User;
+use OCP\Cache\CappedMemoryCache;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Config\IUserMountCache;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\ICacheFactory;
+use OCP\IUser;
+use OCP\IUserManager;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class RootTest
+ *
+ * @package Test\Files\Node
+ */
+class RootTest extends \Test\TestCase {
+ /** @var User */
+ private $user;
+ /** @var \OC\Files\Mount\Manager */
+ private $manager;
+ /** @var IUserMountCache|\PHPUnit\Framework\MockObject\MockObject */
+ private $userMountCache;
+ /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
+ private $logger;
+ /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
+ private $userManager;
+ /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
+ private $eventDispatcher;
+ /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */
+ protected $cacheFactory;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->user = $this->createMock(IUser::class);
+ $this->manager = $this->getMockBuilder(Manager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->userMountCache = $this->getMockBuilder('\OCP\Files\Config\IUserMountCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+ $this->cacheFactory->method('createLocal')
+ ->willReturnCallback(function () {
+ return new ArrayCache();
+ });
+ }
+
+ /**
+ * @return View|\PHPUnit\Framework\MockObject\MockObject $view
+ */
+ protected function getRootViewMock() {
+ $view = $this->createMock(View::class);
+ $view->expects($this->any())
+ ->method('getRoot')
+ ->willReturn('');
+ return $view;
+ }
+
+ protected function getFileInfo($data) {
+ return new FileInfo('', null, '', $data, null);
+ }
+
+ public function testGet(): void {
+ /**
+ * @var Storage $storage
+ */
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $view = $this->getRootViewMock();
+ $root = new Root(
+ $this->manager,
+ $view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+
+ $view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn($this->getFileInfo(['fileid' => 10, 'path' => 'bar/foo', 'name', 'mimetype' => 'text/plain']));
+
+ $root->mount($storage, '');
+ $node = $root->get('/bar/foo');
+ $this->assertEquals(10, $node->getId());
+ $this->assertInstanceOf('\OC\Files\Node\File', $node);
+ }
+
+
+ public function testGetNotFound(): void {
+ $this->expectException(NotFoundException::class);
+
+ /**
+ * @var Storage $storage
+ */
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $view = $this->getRootViewMock();
+ $root = new Root(
+ $this->manager,
+ $view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+
+ $view->expects($this->once())
+ ->method('getFileInfo')
+ ->with('/bar/foo')
+ ->willReturn(false);
+
+ $root->mount($storage, '');
+ $root->get('/bar/foo');
+ }
+
+
+ public function testGetInvalidPath(): void {
+ $this->expectException(NotPermittedException::class);
+
+ $view = $this->getRootViewMock();
+ $root = new Root(
+ $this->manager,
+ $view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+
+ $root->get('/../foo');
+ }
+
+
+ public function testGetNoStorages(): void {
+ $this->expectException(NotFoundException::class);
+
+ $view = $this->getRootViewMock();
+ $root = new Root(
+ $this->manager,
+ $view,
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+
+ $root->get('/bar/foo');
+ }
+
+ public function testGetUserFolder(): void {
+ $root = new Root(
+ $this->manager,
+ $this->getRootViewMock(),
+ $this->user,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+ $user = $this->createMock(IUser::class);
+ $user
+ ->expects($this->once())
+ ->method('getUID')
+ ->willReturn('MyUserId');
+ $this->userManager
+ ->expects($this->once())
+ ->method('get')
+ ->with('MyUserId')
+ ->willReturn($user);
+ /** @var CappedMemoryCache|\PHPUnit\Framework\MockObject\MockObject $cappedMemoryCache */
+ $cappedMemoryCache = $this->createMock(CappedMemoryCache::class);
+ $cappedMemoryCache
+ ->expects($this->once())
+ ->method('hasKey')
+ ->willReturn(true);
+ $folder = $this->createMock(Folder::class);
+ $cappedMemoryCache
+ ->expects($this->once())
+ ->method('get')
+ ->with('MyUserId')
+ ->willReturn($folder);
+
+ $this->invokePrivate($root, 'userFolderCache', [$cappedMemoryCache]);
+ $this->assertEquals($folder, $root->getUserFolder('MyUserId'));
+ }
+
+
+ public function testGetUserFolderWithNoUserObj(): void {
+ $this->expectException(NoUserException::class);
+ $this->expectExceptionMessage('Backends provided no user object');
+
+ $root = new Root(
+ $this->createMock(Manager::class),
+ $this->getRootViewMock(),
+ null,
+ $this->userMountCache,
+ $this->logger,
+ $this->userManager,
+ $this->eventDispatcher,
+ $this->cacheFactory,
+ );
+ $this->userManager
+ ->expects($this->once())
+ ->method('get')
+ ->with('NotExistingUser')
+ ->willReturn(null);
+ $this->logger
+ ->expects($this->once())
+ ->method('error')
+ ->with(
+ 'Backends provided no user object for NotExistingUser',
+ $this->anything()
+ );
+
+ $root->getUserFolder('NotExistingUser');
+ }
+}