diff --git a/.github/workflows/phpunit-mysql.yml b/.github/workflows/phpunit-mysql.yml index c0f4c69b3..8f1b65f51 100644 --- a/.github/workflows/phpunit-mysql.yml +++ b/.github/workflows/phpunit-mysql.yml @@ -92,6 +92,13 @@ jobs: repository: nextcloud/server ref: ${{ matrix.server-versions }} + - name: Checkout Circles + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: nextcloud/circles + ref: master + path: apps/circles + - name: Checkout app uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: @@ -113,6 +120,10 @@ jobs: echo "SET GLOBAL sql_mode=(SELECT CONCAT(@@sql_mode,',ONLY_FULL_GROUP_BY'));" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword echo 'SELECT @@sql_mode;' | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword + - name: Set up Circles dependencies + working-directory: apps/circles + run: composer i + - name: Check composer file existence id: check_composer uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 @@ -131,6 +142,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ app:enable --force circles ./occ app:enable --force ${{ env.APP_NAME }} - name: Check PHPUnit script is defined diff --git a/.github/workflows/phpunit-oci.yml b/.github/workflows/phpunit-oci.yml index d03beb9d1..721af4e36 100644 --- a/.github/workflows/phpunit-oci.yml +++ b/.github/workflows/phpunit-oci.yml @@ -105,6 +105,13 @@ jobs: repository: nextcloud/server ref: ${{ matrix.server-versions }} + - name: Checkout Circles + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: nextcloud/circles + ref: master + path: apps/circles + - name: Checkout app uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: @@ -121,6 +128,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Circles dependencies + working-directory: apps/circles + run: composer i + - name: Check composer file existence id: check_composer uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 @@ -139,6 +150,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=oci --database-name=XE --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=autotest --database-pass=owncloud --admin-user admin --admin-pass admin + ./occ app:enable --force circles ./occ app:enable --force ${{ env.APP_NAME }} - name: Check PHPUnit script is defined diff --git a/.github/workflows/phpunit-pgsql.yml b/.github/workflows/phpunit-pgsql.yml index 2a23e02e6..44a9a85a2 100644 --- a/.github/workflows/phpunit-pgsql.yml +++ b/.github/workflows/phpunit-pgsql.yml @@ -95,6 +95,13 @@ jobs: repository: nextcloud/server ref: ${{ matrix.server-versions }} + - name: Checkout Circles + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: nextcloud/circles + ref: master + path: apps/circles + - name: Checkout app uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: @@ -111,6 +118,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Circles dependencies + working-directory: apps/circles + run: composer i + - name: Check composer file existence id: check_composer uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 @@ -129,6 +140,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ app:enable --force circles ./occ app:enable --force ${{ env.APP_NAME }} - name: Check PHPUnit script is defined diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml index be9e33243..55b28f494 100644 --- a/.github/workflows/phpunit-sqlite.yml +++ b/.github/workflows/phpunit-sqlite.yml @@ -84,6 +84,13 @@ jobs: repository: nextcloud/server ref: ${{ matrix.server-versions }} + - name: Checkout Circles + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: nextcloud/circles + ref: master + path: apps/circles + - name: Checkout app uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: @@ -100,6 +107,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Circles dependencies + working-directory: apps/circles + run: composer i + - name: Check composer file existence id: check_composer uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 @@ -118,6 +129,7 @@ jobs: run: | mkdir data ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ app:enable --force circles ./occ app:enable --force ${{ env.APP_NAME }} - name: Check PHPUnit script is defined diff --git a/lib/Listeners/LoadAdditionalScriptsListener.php b/lib/Listeners/LoadAdditionalScriptsListener.php index baee9d432..f5a9010de 100644 --- a/lib/Listeners/LoadAdditionalScriptsListener.php +++ b/lib/Listeners/LoadAdditionalScriptsListener.php @@ -18,6 +18,10 @@ */ class LoadAdditionalScriptsListener implements IEventListener { public function handle(Event $event): void { + if (!$event instanceof LoadAdditionalScriptsEvent && !$event instanceof BeforeTemplateRenderedEvent) { + return; + } + \OCP\Util::addInitScript('groupfolders', 'groupfolders-init'); \OCP\Util::addScript('groupfolders', 'groupfolders-files'); } diff --git a/lib/Listeners/NodeRenamedListener.php b/lib/Listeners/NodeRenamedListener.php index 84b12c135..b3281519f 100644 --- a/lib/Listeners/NodeRenamedListener.php +++ b/lib/Listeners/NodeRenamedListener.php @@ -26,24 +26,36 @@ public function __construct( } public function handle(Event $event): void { - $source = $event->getSource(); + if (!$event instanceof NodeRenamedEvent) { + return; + } + $target = $event->getTarget(); - // Look at the parent because the node itself is not existing anymore - $sourceStorage = $source->getParent()->getStorage(); + if (!$target instanceof Folder) { + return; + } + $targetStorage = $target->getStorage(); + if (!$targetStorage->instanceOfStorage(GroupFolderStorage::class)) { + return; + } + + $source = $event->getSource(); + // Look at the parent because the node itself is not existing anymore + $sourceParent = $source->getParent(); + $sourceParentStorage = $sourceParent->getStorage(); + if (!$sourceParentStorage->instanceOfStorage(GroupFolderStorage::class)) { + return; + } - if (($target instanceof Folder) && - $sourceStorage->instanceOfStorage(GroupFolderStorage::class) && - $targetStorage->instanceOfStorage(GroupFolderStorage::class)) { - // Get internal path on parent to avoid NotFoundException - $sourcePath = $source->getParent()->getInternalPath(); - if ($sourcePath !== '') { - $sourcePath .= '/'; - } - - $sourcePath .= $source->getName(); - $targetPath = $target->getInternalPath(); - $this->trashManager->updateTrashedChildren($sourceStorage->getFolderId(), $targetStorage->getFolderId(), $sourcePath, $targetPath); + // Get internal path on parent to avoid NotFoundException + $sourceParentPath = $sourceParent->getInternalPath(); + if ($sourceParentPath !== '') { + $sourceParentPath .= '/'; } + + $sourceParentPath .= $source->getName(); + $targetPath = $target->getInternalPath(); + $this->trashManager->updateTrashedChildren($sourceParentStorage->getFolderId(), $targetStorage->getFolderId(), $sourceParentPath, $targetPath); } } diff --git a/tests/Listeners/CircleDestroyedEventListenerTest.php b/tests/Listeners/CircleDestroyedEventListenerTest.php new file mode 100644 index 000000000..06f695f18 --- /dev/null +++ b/tests/Listeners/CircleDestroyedEventListenerTest.php @@ -0,0 +1,63 @@ +folderManager = $this->createMock(FolderManager::class); + + $this->listener = new CircleDestroyedEventListener($this->folderManager); + } + + public function testHandleInvalid(): void { + $event = $this->createMock(Event::class); + + $this->folderManager + ->expects($this->never()) + ->method('deleteCircle'); + + /** @psalm-suppress InvalidArgument on purpose */ + $this->listener->handle($event); + } + + public function testHandle(): void { + $circle = $this->createMock(Circle::class); + $circle + ->expects($this->once()) + ->method('getSingleId') + ->willReturn('123'); + + $event = $this->createMock(CircleDestroyedEvent::class); + $event + ->expects($this->once()) + ->method('getCircle') + ->willReturn($circle); + + $this->folderManager + ->expects($this->once()) + ->method('deleteCircle') + ->with('123'); + + $this->listener->handle($event); + } +} diff --git a/tests/Listeners/LoadAdditionalScriptsListenerTest.php b/tests/Listeners/LoadAdditionalScriptsListenerTest.php new file mode 100644 index 000000000..c549fe99c --- /dev/null +++ b/tests/Listeners/LoadAdditionalScriptsListenerTest.php @@ -0,0 +1,51 @@ +listener = new LoadAdditionalScriptsListener(); + } + + public static function handleProvider(): array { + $expectedScripts = [ + 'groupfolders/l10n/en', + 'groupfolders/js/groupfolders-init', + 'groupfolders/js/groupfolders-files', + ]; + return [ + [Event::class, []], + [LoadAdditionalScriptsEvent::class, $expectedScripts], + [BeforeTemplateRenderedEvent::class, $expectedScripts], + ]; + } + + /** + * @dataProvider handleProvider + * @param class-string $class + */ + public function testHandle(string $class, array $expectedScripts): void { + $event = $this->createMock($class); + + $this->listener->handle($event); + $this->assertEquals($expectedScripts, array_values(Util::getScripts())); + } +} diff --git a/tests/Listeners/NodeRenamedListenerTest.php b/tests/Listeners/NodeRenamedListenerTest.php new file mode 100644 index 000000000..9ea7ba706 --- /dev/null +++ b/tests/Listeners/NodeRenamedListenerTest.php @@ -0,0 +1,214 @@ +trashManager = $this->createMock(TrashManager::class); + + $this->listener = new NodeRenamedListener($this->trashManager); + + $this->sourceParentStorage = $this->createMock(GroupFolderStorage::class); + $this->sourceParentStorage + ->expects($this->any()) + ->method('getFolderId') + ->willReturn(1); + + $this->sourceParent = $this->createMock(Folder::class); + $this->sourceParent + ->expects($this->any()) + ->method('getStorage') + ->willReturn($this->sourceParentStorage); + + $this->source = $this->createMock(File::class); + $this->source + ->expects($this->any()) + ->method('getParent') + ->willReturn($this->sourceParent); + $this->source + ->expects($this->any()) + ->method('getName') + ->willReturn('test.txt'); + + $this->targetStorage = $this->createMock(GroupFolderStorage::class); + $this->targetStorage + ->expects($this->any()) + ->method('getFolderId') + ->willReturn(2); + + $this->event = $this->createMock(NodeRenamedEvent::class); + $this->event + ->expects($this->any()) + ->method('getSource') + ->willReturn($this->source); + $this->event + ->expects($this->any()) + ->method('getTarget') + ->willReturnCallback(fn (): MockObject => $this->target); + } + + public function testHandleInvalid(): void { + $event = $this + ->getMockBuilder(Event::class) + ->addMethods(['getSource']) + ->getMock(); + $event + ->expects($this->never()) + ->method('getSource'); + + /** @psalm-suppress InvalidArgument on purpose */ + $this->listener->handle($event); + } + + public function testHandle(): void { + $this->sourceParent + ->expects($this->once()) + ->method('getInternalPath') + ->willReturn('abc'); + + $this->sourceParentStorage + ->expects($this->once()) + ->method('instanceOfStorage') + ->willReturn(true); + + $this->target = $this->createMock(Folder::class); + $this->target + ->expects($this->once()) + ->method('getStorage') + ->willReturn($this->targetStorage); + $this->target + ->expects($this->once()) + ->method('getInternalPath') + ->willReturn('def'); + + $this->targetStorage + ->expects($this->once()) + ->method('instanceOfStorage') + ->willReturn(true); + + $this->trashManager + ->expects($this->once()) + ->method('updateTrashedChildren') + ->with(1, 2, 'abc/test.txt', 'def'); + + $this->listener->handle($this->event); + } + + public function testHandleInRootFolder(): void { + $this->sourceParent + ->expects($this->once()) + ->method('getInternalPath') + ->willReturn(''); + + $this->sourceParentStorage + ->expects($this->once()) + ->method('instanceOfStorage') + ->willReturn(true); + + $this->target = $this->createMock(Folder::class); + $this->target + ->expects($this->once()) + ->method('getStorage') + ->willReturn($this->targetStorage); + $this->target + ->expects($this->once()) + ->method('getInternalPath') + ->willReturn('def'); + + $this->targetStorage + ->expects($this->once()) + ->method('instanceOfStorage') + ->willReturn(true); + + $this->trashManager + ->expects($this->once()) + ->method('updateTrashedChildren') + ->with(1, 2, 'test.txt', 'def'); + + $this->listener->handle($this->event); + } + + public function testHandleTargetNotAFolder(): void { + $this->target = $this->createMock(File::class); + + $this->trashManager + ->expects($this->never()) + ->method('updateTrashedChildren'); + + $this->listener->handle($this->event); + } + + public function testHandleSourceNotAGroupfolder(): void { + $this->target = $this->createMock(Folder::class); + $this->target + ->expects($this->once()) + ->method('getStorage') + ->willReturn($this->targetStorage); + + $this->targetStorage + ->expects($this->once()) + ->method('instanceOfStorage') + ->willReturn(true); + + $this->sourceParentStorage + ->expects($this->once()) + ->method('instanceOfStorage') + ->willReturn(false); + + $this->trashManager + ->expects($this->never()) + ->method('updateTrashedChildren'); + + $this->listener->handle($this->event); + } + + + public function testHandleTargetNotAGroupfolder(): void { + $this->target = $this->createMock(Folder::class); + $this->target + ->expects($this->once()) + ->method('getStorage') + ->willReturn($this->targetStorage); + + $this->targetStorage + ->expects($this->once()) + ->method('instanceOfStorage') + ->willReturn(false); + + $this->trashManager + ->expects($this->never()) + ->method('updateTrashedChildren'); + + $this->listener->handle($this->event); + } + +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index cdc08c490..c7dfa45b4 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -12,6 +12,6 @@ \OC::$composerAutoloader->addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/', true); \OC::$composerAutoloader->addPsr4('Tests\\', OC::$SERVERROOT . '/tests/', true); -OC_App::loadApp('groupfolders'); +OC_App::loadApps(['groupfolders', 'circles']); OC_Hook::clear();