* This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ 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\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 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, \OC::$server->getUserManager()->get($this->userId), \OC::$server->getUserMountCache(), $this->createMock(LoggerInterface::class), $this->createMock(IUserManager::class), $this->createMock(IEventDispatcher::class), $cacheFactory, ); $this->eventDispatcher = \OC::$server->query(IEventDispatcher::class); $this->logger = \OC::$server->query(LoggerInterface::class); } protected function tearDown(): void { parent::tearDown(); \OC_Hook::clear('OC_Filesystem'); \OC_Util::tearDownFS(); } public function viewToNodeProvider() { return [ [function () { Filesystem::file_put_contents('test.txt', 'asd'); }, 'preWrite', '\OCP\Files::preWrite', BeforeNodeWrittenEvent::class], [function () { Filesystem::file_put_contents('test.txt', 'asd'); }, 'postWrite', '\OCP\Files::postWrite', NodeWrittenEvent::class], [function () { Filesystem::file_put_contents('test.txt', 'asd'); }, 'preCreate', '\OCP\Files::preCreate', BeforeNodeCreatedEvent::class], [function () { Filesystem::file_put_contents('test.txt', 'asd'); }, 'postCreate', '\OCP\Files::postCreate', NodeCreatedEvent::class], [function () { Filesystem::mkdir('test.txt'); }, 'preCreate', '\OCP\Files::preCreate', BeforeNodeCreatedEvent::class], [function () { Filesystem::mkdir('test.txt'); }, 'postCreate', '\OCP\Files::postCreate', NodeCreatedEvent::class], [function () { Filesystem::touch('test.txt'); }, 'preTouch', '\OCP\Files::preTouch', BeforeNodeTouchedEvent::class], [function () { Filesystem::touch('test.txt'); }, 'postTouch', '\OCP\Files::postTouch', NodeTouchedEvent::class], [function () { Filesystem::touch('test.txt'); }, 'preCreate', '\OCP\Files::preCreate', BeforeNodeCreatedEvent::class], [function () { Filesystem::touch('test.txt'); }, 'postCreate', '\OCP\Files::postCreate', NodeCreatedEvent::class], [function () { Filesystem::file_put_contents('test.txt', 'asd'); Filesystem::unlink('test.txt'); }, 'preDelete', '\OCP\Files::preDelete', BeforeNodeDeletedEvent::class], [function () { Filesystem::file_put_contents('test.txt', 'asd'); Filesystem::unlink('test.txt'); }, 'postDelete', '\OCP\Files::postDelete', NodeDeletedEvent::class], [function () { Filesystem::mkdir('test.txt'); Filesystem::rmdir('test.txt'); }, 'preDelete', '\OCP\Files::preDelete', BeforeNodeDeletedEvent::class], [function () { Filesystem::mkdir('test.txt'); Filesystem::rmdir('test.txt'); }, 'postDelete', '\OCP\Files::postDelete', NodeDeletedEvent::class], ]; } /** * @param callable $operation * @param string $expectedHook * @dataProvider viewToNodeProvider */ public function testViewToNode(callable $operation, $expectedHook, $expectedLegacyEvent, $expectedEvent) { $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) { $hookCalled = true; $hookNode = $node; }); $dispatcherCalled = false; /** @var Node $dispatcherNode */ $dispatcherNode = null; $this->eventDispatcher->addListener($expectedLegacyEvent, function ($event) use (&$dispatcherCalled, &$dispatcherNode) { /** @var GenericEvent|APIGenericEvent $event */ $dispatcherCalled = true; $dispatcherNode = $event->getSubject(); }); $newDispatcherCalled = false; $newDispatcherNode = null; $this->eventDispatcher->addListener($expectedEvent, function ($event) use ($expectedEvent, &$newDispatcherCalled, &$newDispatcherNode) { 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 function viewToNodeProviderCopyRename() { return [ [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::rename('source', 'target'); }, 'preRename', '\OCP\Files::preRename', BeforeNodeRenamedEvent::class], [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::rename('source', 'target'); }, 'postRename', '\OCP\Files::postRename', NodeRenamedEvent::class], [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::copy('source', 'target'); }, 'preCopy', '\OCP\Files::preCopy', BeforeNodeCopiedEvent::class], [function () { Filesystem::file_put_contents('source', 'asd'); Filesystem::copy('source', 'target'); }, 'postCopy', '\OCP\Files::postCopy', NodeCopiedEvent::class], ]; } /** * @param callable $operation * @param string $expectedHook * @dataProvider viewToNodeProviderCopyRename */ public function testViewToNodeCopyRename(callable $operation, $expectedHook, $expectedLegacyEvent, $expectedEvent) { $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) { $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) { /** @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) { 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() { $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) { $hookCalled = true; $hookNode = $node; }); $dispatcherCalled = false; /** @var Node $dispatcherNode */ $dispatcherNode = null; $this->eventDispatcher->addListener('\OCP\Files::postDelete', function ($event) use (&$dispatcherCalled, &$dispatcherNode) { /** @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) { 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()); } }