From 8b6eda26539144e94dcde737d0d183d08fdd9de1 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 24 Sep 2026 14:00:45 +0200 Subject: [PATCH 1/2] test(files_sharing): Check mount events are dispatched for share recipients Apps keep per-user data in sync with file access by listening to UserMountAddedEvent and UserMountRemovedEvent. When a share is created or deleted, these events should reach the recipient at the latest when the recipient next sets up their file system. Assisted-by: ClaudeCode:claude-opus-5.5 Signed-off-by: Marcel Klehr --- .../tests/ShareMountEventsTest.php | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 apps/files_sharing/tests/ShareMountEventsTest.php diff --git a/apps/files_sharing/tests/ShareMountEventsTest.php b/apps/files_sharing/tests/ShareMountEventsTest.php new file mode 100644 index 0000000000000..02faac5783f47 --- /dev/null +++ b/apps/files_sharing/tests/ShareMountEventsTest.php @@ -0,0 +1,130 @@ + */ + private array $events = []; + /** @var \Closure(Event): void */ + private \Closure $listener; + + protected function setUp(): void { + parent::setUp(); + + $this->eventDispatcher = Server::get(IEventDispatcher::class); + $this->listener = function (Event $event): void { + $this->events[] = $event; + }; + $this->eventDispatcher->addListener(UserMountAddedEvent::class, $this->listener); + $this->eventDispatcher->addListener(UserMountRemovedEvent::class, $this->listener); + + $this->rootFolder->getUserFolder(self::TEST_FILES_SHARING_API_USER1)->newFolder(self::FOLDER); + + // The recipient has used their file system before + $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + $this->loginHelper(self::TEST_FILES_SHARING_API_USER1); + $this->events = []; + } + + protected function tearDown(): void { + $this->eventDispatcher->removeListener(UserMountAddedEvent::class, $this->listener); + $this->eventDispatcher->removeListener(UserMountRemovedEvent::class, $this->listener); + Server::get(SharesUpdatedListener::class)->setCutOffMarkTime(-1); + + parent::tearDown(); + } + + /** + * @return array + */ + public static function shareProvider(): array { + // A cutoff time of -1 updates the share mounts of recipients immediately, + // 0 marks recipients so their share mounts are updated on their next setup + return [ + 'user share, immediate update' => [IShare::TYPE_USER, self::TEST_FILES_SHARING_API_USER2, -1], + 'user share, deferred update' => [IShare::TYPE_USER, self::TEST_FILES_SHARING_API_USER2, 0], + 'group share, immediate update' => [IShare::TYPE_GROUP, self::TEST_FILES_SHARING_API_GROUP1, -1], + 'group share, deferred update' => [IShare::TYPE_GROUP, self::TEST_FILES_SHARING_API_GROUP1, 0], + ]; + } + + #[DataProvider(methodName: 'shareProvider')] + public function testMountAddedEventForCreatedShare(int $shareType, string $recipient, float $cutOffTime): void { + Server::get(SharesUpdatedListener::class)->setCutOffMarkTime($cutOffTime); + + $this->share($shareType, self::FOLDER, self::TEST_FILES_SHARING_API_USER1, $recipient, Constants::PERMISSION_ALL); + $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + + $this->assertEquals( + ['/' . self::TEST_FILES_SHARING_API_USER2 . '/files/' . self::FOLDER . '/'], + $this->getMountPointsOfEvents(UserMountAddedEvent::class, self::TEST_FILES_SHARING_API_USER2), + 'A UserMountAddedEvent should be dispatched for the share mount of the recipient', + ); + } + + #[DataProvider(methodName: 'shareProvider')] + public function testMountRemovedEventForDeletedShare(int $shareType, string $recipient, float $cutOffTime): void { + $share = $this->share($shareType, self::FOLDER, self::TEST_FILES_SHARING_API_USER1, $recipient, Constants::PERMISSION_ALL); + $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + $this->loginHelper(self::TEST_FILES_SHARING_API_USER1); + $this->events = []; + + Server::get(SharesUpdatedListener::class)->setCutOffMarkTime($cutOffTime); + $this->shareManager->deleteShare($share); + $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + + $this->assertEquals( + ['/' . self::TEST_FILES_SHARING_API_USER2 . '/files/' . self::FOLDER . '/'], + $this->getMountPointsOfEvents(UserMountRemovedEvent::class, self::TEST_FILES_SHARING_API_USER2), + 'A UserMountRemovedEvent should be dispatched for the share mount of the recipient', + ); + } + + /** + * Log in as the user and access their files, as a new request by them would + */ + private function setupFileSystem(string $userId): void { + $this->loginHelper($userId); + $this->rootFolder->getUserFolder($userId)->getDirectoryListing(); + } + + /** + * @param class-string $eventClass + * @return list + */ + private function getMountPointsOfEvents(string $eventClass, string $userId): array { + $mountPoints = []; + foreach ($this->events as $event) { + if ($event instanceof $eventClass && $event->mountPoint->getUser()->getUID() === $userId) { + $mountPoints[] = $event->mountPoint->getMountPoint(); + } + } + return $mountPoints; + } +} From 6134fa11c23981ee65c4ef067fcd8248cf245718 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 24 Sep 2026 16:15:35 +0200 Subject: [PATCH 2/2] fix: Address review comments Signed-off-by: Marcel Klehr --- .../tests/ShareMountEventsTest.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/files_sharing/tests/ShareMountEventsTest.php b/apps/files_sharing/tests/ShareMountEventsTest.php index 02faac5783f47..28cd0bdcdcd67 100644 --- a/apps/files_sharing/tests/ShareMountEventsTest.php +++ b/apps/files_sharing/tests/ShareMountEventsTest.php @@ -13,6 +13,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\Event\UserMountAddedEvent; use OCP\Files\Config\Event\UserMountRemovedEvent; +use OCP\Files\Folder; use OCP\Server; use OCP\Share\IShare; use PHPUnit\Framework\Attributes\DataProvider; @@ -47,7 +48,7 @@ protected function setUp(): void { $this->rootFolder->getUserFolder(self::TEST_FILES_SHARING_API_USER1)->newFolder(self::FOLDER); // The recipient has used their file system before - $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + $this->assertFalse($this->getUserFolder(self::TEST_FILES_SHARING_API_USER2)->nodeExists(self::FOLDER)); $this->loginHelper(self::TEST_FILES_SHARING_API_USER1); $this->events = []; } @@ -79,7 +80,7 @@ public function testMountAddedEventForCreatedShare(int $shareType, string $recip Server::get(SharesUpdatedListener::class)->setCutOffMarkTime($cutOffTime); $this->share($shareType, self::FOLDER, self::TEST_FILES_SHARING_API_USER1, $recipient, Constants::PERMISSION_ALL); - $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + $this->getUserFolder(self::TEST_FILES_SHARING_API_USER2)->get(self::FOLDER); $this->assertEquals( ['/' . self::TEST_FILES_SHARING_API_USER2 . '/files/' . self::FOLDER . '/'], @@ -91,13 +92,16 @@ public function testMountAddedEventForCreatedShare(int $shareType, string $recip #[DataProvider(methodName: 'shareProvider')] public function testMountRemovedEventForDeletedShare(int $shareType, string $recipient, float $cutOffTime): void { $share = $this->share($shareType, self::FOLDER, self::TEST_FILES_SHARING_API_USER1, $recipient, Constants::PERMISSION_ALL); - $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + $this->getUserFolder(self::TEST_FILES_SHARING_API_USER2)->get(self::FOLDER); $this->loginHelper(self::TEST_FILES_SHARING_API_USER1); $this->events = []; Server::get(SharesUpdatedListener::class)->setCutOffMarkTime($cutOffTime); $this->shareManager->deleteShare($share); - $this->setupFileSystem(self::TEST_FILES_SHARING_API_USER2); + $this->assertFalse( + $this->getUserFolder(self::TEST_FILES_SHARING_API_USER2)->nodeExists(self::FOLDER), + 'The share should no longer be mounted for the recipient', + ); $this->assertEquals( ['/' . self::TEST_FILES_SHARING_API_USER2 . '/files/' . self::FOLDER . '/'], @@ -107,11 +111,11 @@ public function testMountRemovedEventForDeletedShare(int $shareType, string $rec } /** - * Log in as the user and access their files, as a new request by them would + * Log in as the user and get their user folder, as a new request by them would */ - private function setupFileSystem(string $userId): void { + private function getUserFolder(string $userId): Folder { $this->loginHelper($userId); - $this->rootFolder->getUserFolder($userId)->getDirectoryListing(); + return $this->rootFolder->getUserFolder($userId); } /**