diff options
Diffstat (limited to 'tests/lib/Collaboration')
9 files changed, 3430 insertions, 0 deletions
diff --git a/tests/lib/Collaboration/Collaborators/GroupPluginTest.php b/tests/lib/Collaboration/Collaborators/GroupPluginTest.php new file mode 100644 index 00000000000..a4ecd598562 --- /dev/null +++ b/tests/lib/Collaboration/Collaborators/GroupPluginTest.php @@ -0,0 +1,503 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Collaborators; + +use OC\Collaboration\Collaborators\GroupPlugin; +use OC\Collaboration\Collaborators\SearchResult; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserSession; +use OCP\Share\IShare; +use Test\TestCase; + +class GroupPluginTest extends TestCase { + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $groupManager; + + /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ + protected $session; + + /** @var ISearchResult */ + protected $searchResult; + + /** @var GroupPlugin */ + protected $plugin; + + /** @var int */ + protected $limit = 2; + + /** @var int */ + protected $offset = 0; + + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject */ + protected $user; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + + $this->groupManager = $this->createMock(IGroupManager::class); + + $this->session = $this->createMock(IUserSession::class); + + $this->searchResult = new SearchResult(); + + $this->user = $this->getUserMock('admin', 'Administrator'); + } + + public function instantiatePlugin() { + // cannot be done within setUp, because dependent mocks needs to be set + // up with configuration etc. first + $this->plugin = new GroupPlugin( + $this->config, + $this->groupManager, + $this->session + ); + } + + public function getUserMock($uid, $displayName) { + $user = $this->createMock(IUser::class); + + $user->expects($this->any()) + ->method('getUID') + ->willReturn($uid); + + $user->expects($this->any()) + ->method('getDisplayName') + ->willReturn($displayName); + + return $user; + } + + /** + * @param string $gid + * @param null $displayName + * @param bool $hide + * @return IGroup|\PHPUnit\Framework\MockObject\MockObject + */ + protected function getGroupMock($gid, $displayName = null, $hide = false) { + $group = $this->createMock(IGroup::class); + + $group->expects($this->any()) + ->method('getGID') + ->willReturn($gid); + + if (is_null($displayName)) { + // note: this is how the Group class behaves + $displayName = $gid; + } + + $group->expects($this->any()) + ->method('getDisplayName') + ->willReturn($displayName); + + $group->method('hideFromCollaboration') + ->willReturn($hide); + + return $group; + } + + public function dataGetGroups(): array { + return [ + ['test', false, true, false, [], [], [], [], true, false], + ['test', false, false, false, [], [], [], [], true, false], + // group sharing disabled + ['test', false, true, true, [], [], [], [], false, false], + // group without display name + [ + 'test', false, true, false, + [$this->getGroupMock('test1')], + [], + [], + [['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + true, + false, + ], + // group with display name, search by id + [ + 'test', false, true, false, + [$this->getGroupMock('test1', 'Test One')], + [], + [], + [['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + true, + false, + ], + // group with display name, search by display name + [ + 'one', false, true, false, + [$this->getGroupMock('test1', 'Test One')], + [], + [], + [['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + true, + false, + ], + // group with display name, search by display name, exact expected + [ + 'Test One', false, true, false, + [$this->getGroupMock('test1', 'Test One')], + [], + [['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + [], + true, + false, + ], + [ + 'test', false, false, false, + [$this->getGroupMock('test1')], + [], + [], + [], + true, + false, + ], + [ + 'test', false, true, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [], + [['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']]], + [['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', false, false, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [], + [['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']]], + [], + true, + false, + ], + [ + 'test', false, true, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [], + [ + ['label' => 'test0', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + null, + ], + [ + 'test', false, false, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [], + [], + true, + null, + ], + [ + 'test', false, true, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [ + ['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']], + ], + [ + ['label' => 'test0', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + $this->getGroupMock('test'), + ], + [ + 'test', false, false, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [], + [ + ['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']], + ], + [], + true, + $this->getGroupMock('test'), + ], + ['test', true, true, false, [], [], [], [], true, false], + ['test', true, false, false, [], [], [], [], true, false], + [ + 'test', true, true, false, + [ + $this->getGroupMock('test1'), + $this->getGroupMock('test2'), + ], + [$this->getGroupMock('test1')], + [], + [['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', true, false, false, + [ + $this->getGroupMock('test1'), + $this->getGroupMock('test2'), + ], + [$this->getGroupMock('test1')], + [], + [], + true, + false, + ], + [ + 'test', true, true, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test')], + [['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']]], + [], + false, + false, + ], + [ + 'test', true, false, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test')], + [['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']]], + [], + true, + false, + ], + [ + 'test', true, true, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test1')], + [], + [['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', true, false, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test1')], + [], + [], + true, + false, + ], + [ + 'test', true, true, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']]], + [['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']]], + false, + false, + ], + [ + 'test', true, false, false, + [ + $this->getGroupMock('test'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']]], + [], + true, + false, + ], + [ + 'test', true, true, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [], + [ + ['label' => 'test0', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + null, + ], + [ + 'test', true, false, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [], + [], + true, + null, + ], + [ + 'test', true, true, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [ + ['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']], + ], + [ + ['label' => 'test0', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test0']], + ['label' => 'test1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test1']], + ], + false, + $this->getGroupMock('test'), + ], + [ + 'test', true, false, false, + [ + $this->getGroupMock('test0'), + $this->getGroupMock('test1'), + ], + [$this->getGroupMock('test'), $this->getGroupMock('test0'), $this->getGroupMock('test1')], + [ + ['label' => 'test', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'test']], + ], + [], + true, + $this->getGroupMock('test'), + ], + [ + 'test', false, false, false, + [ + $this->getGroupMock('test', null, true), + $this->getGroupMock('test1'), + ], + [], + [], + [], + true, + false, + ], + ]; + } + + /** + * + * @param string $searchTerm + * @param bool $shareWithGroupOnly + * @param bool $shareeEnumeration + * @param bool $groupSharingDisabled + * @param array $groupResponse + * @param array $userGroupsResponse + * @param array $exactExpected + * @param array $expected + * @param bool $reachedEnd + * @param bool|IGroup $singleGroup + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetGroups')] + public function testSearch( + string $searchTerm, + bool $shareWithGroupOnly, + bool $shareeEnumeration, + bool $groupSharingDisabled, + array $groupResponse, + array $userGroupsResponse, + array $exactExpected, + array $expected, + bool $reachedEnd, + $singleGroup, + ): void { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback( + function ($appName, $key, $default) use ($shareWithGroupOnly, $shareeEnumeration, $groupSharingDisabled) { + if ($appName !== 'core') { + return $default; + } + switch ($key) { + case 'shareapi_only_share_with_group_members': + return $shareWithGroupOnly ? 'yes' : 'no'; + case 'shareapi_allow_share_dialog_user_enumeration': + return $shareeEnumeration ? 'yes' : 'no'; + case 'shareapi_allow_group_sharing': + return $groupSharingDisabled ? 'no' : 'yes'; + default: + return $default; + } + } + ); + + $this->instantiatePlugin(); + + if (!$groupSharingDisabled) { + $this->groupManager->expects($this->once()) + ->method('search') + ->with($searchTerm, $this->limit, $this->offset) + ->willReturn($groupResponse); + } + + if ($singleGroup !== false) { + $this->groupManager->expects($this->once()) + ->method('get') + ->with($searchTerm) + ->willReturn($singleGroup); + } + + if ($shareWithGroupOnly) { + $this->session->expects($this->any()) + ->method('getUser') + ->willReturn($this->user); + + $numGetUserGroupsCalls = empty($groupResponse) ? 0 : 1; + $this->groupManager->expects($this->exactly($numGetUserGroupsCalls)) + ->method('getUserGroups') + ->with($this->user) + ->willReturn($userGroupsResponse); + } + + $moreResults = $this->plugin->search($searchTerm, $this->limit, $this->offset, $this->searchResult); + $result = $this->searchResult->asArray(); + + if (!$groupSharingDisabled) { + $this->assertEquals($exactExpected, $result['exact']['groups']); + $this->assertEquals($expected, $result['groups']); + } + $this->assertSame($reachedEnd, $moreResults); + } +} diff --git a/tests/lib/Collaboration/Collaborators/LookupPluginTest.php b/tests/lib/Collaboration/Collaborators/LookupPluginTest.php new file mode 100644 index 00000000000..ac9b196ea1e --- /dev/null +++ b/tests/lib/Collaboration/Collaborators/LookupPluginTest.php @@ -0,0 +1,498 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Collaborators; + +use OC\Collaboration\Collaborators\LookupPlugin; +use OC\Federation\CloudId; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Federation\ICloudId; +use OCP\Federation\ICloudIdManager; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use OCP\Http\Client\IResponse; +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserSession; +use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class LookupPluginTest extends TestCase { + /** @var IConfig|MockObject */ + protected $config; + /** @var IClientService|MockObject */ + protected $clientService; + /** @var IUserSession|MockObject */ + protected $userSession; + /** @var ICloudIdManager|MockObject */ + protected $cloudIdManager; + /** @var LookupPlugin */ + protected $plugin; + /** @var LoggerInterface|MockObject */ + protected $logger; + + protected function setUp(): void { + parent::setUp(); + + $this->userSession = $this->createMock(IUserSession::class); + $this->cloudIdManager = $this->createMock(ICloudIdManager::class); + $this->config = $this->createMock(IConfig::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->clientService = $this->createMock(IClientService::class); + $cloudId = $this->createMock(ICloudId::class); + $cloudId->expects($this->any())->method('getRemote')->willReturn('myNextcloud.net'); + $user = $this->createMock(IUser::class); + $user->expects($this->any())->method('getCloudId')->willReturn('user@myNextcloud.net'); + $this->userSession->expects($this->any())->method('getUser') + ->willReturn($user); + $this->cloudIdManager->expects($this->any())->method('resolveCloudId') + ->willReturnCallback(function ($cloudId) { + if ($cloudId === 'user@myNextcloud.net') { + return new CloudId('user@myNextcloud.net', 'user', 'myNextcloud.net'); + } + return new CloudId('user@someNextcloud.net', 'user', 'someNextcloud.net'); + }); + + + $this->plugin = new LookupPlugin( + $this->config, + $this->clientService, + $this->userSession, + $this->cloudIdManager, + $this->logger + ); + } + + public function testSearchNoLookupServerURI(): void { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('files_sharing', 'lookupServerEnabled', 'no') + ->willReturn('yes'); + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['gs.enabled', false, true], + ['has_internet_connection', true, true], + ]); + + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('lookup_server', 'https://lookup.nextcloud.com') + ->willReturn(''); + + $this->clientService->expects($this->never()) + ->method('newClient'); + + /** @var ISearchResult|MockObject $searchResult */ + $searchResult = $this->createMock(ISearchResult::class); + + $this->plugin->search('foobar', 10, 0, $searchResult); + } + + public function testSearchNoInternet(): void { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('files_sharing', 'lookupServerEnabled', 'no') + ->willReturn('yes'); + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['gs.enabled', false, false], + ['has_internet_connection', true, false], + ]); + + $this->clientService->expects($this->never()) + ->method('newClient'); + + /** @var ISearchResult|MockObject $searchResult */ + $searchResult = $this->createMock(ISearchResult::class); + + $this->plugin->search('foobar', 10, 0, $searchResult); + } + + /** + * @param array $searchParams + */ + #[\PHPUnit\Framework\Attributes\DataProvider('searchDataProvider')] + public function testSearch(array $searchParams): void { + $type = new SearchResultType('lookup'); + + /** @var ISearchResult|MockObject $searchResult */ + $searchResult = $this->createMock(ISearchResult::class); + $searchResult->expects($this->once()) + ->method('addResultSet') + ->with($type, $searchParams['expectedResult'], []); + + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('files_sharing', 'lookupServerEnabled', 'no') + ->willReturn('yes'); + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['gs.enabled', false, true], + ['has_internet_connection', true, true], + ]); + + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('lookup_server', 'https://lookup.nextcloud.com') + ->willReturn($searchParams['server']); + + $response = $this->createMock(IResponse::class); + $response->expects($this->once()) + ->method('getBody') + ->willReturn(json_encode($searchParams['resultBody'])); + + $client = $this->createMock(IClient::class); + $client->expects($this->once()) + ->method('get') + ->willReturnCallback(function ($url) use ($searchParams, $response) { + $this->assertSame(strpos($url, $searchParams['server'] . '/users?search='), 0); + $this->assertNotFalse(strpos($url, urlencode($searchParams['search']))); + return $response; + }); + + $this->clientService->expects($this->once()) + ->method('newClient') + ->willReturn($client); + + $moreResults = $this->plugin->search( + $searchParams['search'], + $searchParams['limit'], + $searchParams['offset'], + $searchResult + ); + + $this->assertFalse($moreResults); + } + + + /** + * @param array $searchParams + * @param bool $GSEnabled + * @param bool $LookupEnabled + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEnableDisableLookupServer')] + public function testSearchEnableDisableLookupServer(array $searchParams, $GSEnabled, $LookupEnabled): void { + $type = new SearchResultType('lookup'); + + /** @var ISearchResult|MockObject $searchResult */ + $searchResult = $this->createMock(ISearchResult::class); + + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('files_sharing', 'lookupServerEnabled', 'no') + ->willReturn($LookupEnabled ? 'yes' : 'no'); + if ($GSEnabled) { + $searchResult->expects($this->once()) + ->method('addResultSet') + ->with($type, $searchParams['expectedResult'], []); + + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['gs.enabled', false, $GSEnabled], + ['has_internet_connection', true, true], + ]); + $this->config->expects($this->once()) + ->method('getSystemValueString') + ->with('lookup_server', 'https://lookup.nextcloud.com') + ->willReturn($searchParams['server']); + + $response = $this->createMock(IResponse::class); + $response->expects($this->once()) + ->method('getBody') + ->willReturn(json_encode($searchParams['resultBody'])); + + $client = $this->createMock(IClient::class); + $client->expects($this->once()) + ->method('get') + ->willReturnCallback(function ($url) use ($searchParams, $response) { + $this->assertSame(strpos($url, $searchParams['server'] . '/users?search='), 0); + $this->assertNotFalse(strpos($url, urlencode($searchParams['search']))); + return $response; + }); + + $this->clientService->expects($this->once()) + ->method('newClient') + ->willReturn($client); + } else { + $searchResult->expects($this->never())->method('addResultSet'); + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['gs.enabled', false, $GSEnabled], + ['has_internet_connection', true, true], + ]); + } + $moreResults = $this->plugin->search( + $searchParams['search'], + $searchParams['limit'], + $searchParams['offset'], + $searchResult + ); + + $this->assertFalse($moreResults); + } + + + public function testSearchGSDisabled(): void { + $this->config->expects($this->atLeastOnce()) + ->method('getSystemValueBool') + ->willReturnMap([ + ['has_internet_connection', true, true], + ['gs.enabled', false, false], + ]); + + /** @var ISearchResult|MockObject $searchResult */ + $searchResult = $this->createMock(ISearchResult::class); + $searchResult->expects($this->never()) + ->method('addResultSet'); + $searchResult->expects($this->never()) + ->method('markExactIdMatch'); + + $this->assertFalse($this->plugin->search('irr', 10, 0, $searchResult)); + } + + public static function dataSearchEnableDisableLookupServer(): array { + $fedIDs = [ + 'foo@enceladus.moon', + 'foobar@enceladus.moon', + 'foongus@enceladus.moon', + ]; + + return [ + [[ + 'search' => 'foo', + 'limit' => 10, + 'offset' => 0, + 'server' => 'https://lookup.example.io', + 'resultBody' => [ + ['federationId' => $fedIDs[0]], + ['federationId' => $fedIDs[1]], + ['federationId' => $fedIDs[2]], + ], + 'expectedResult' => [ + [ + 'label' => $fedIDs[0], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[0] + ], + 'extra' => ['federationId' => $fedIDs[0]], + ], + [ + 'label' => $fedIDs[1], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[1] + ], + 'extra' => ['federationId' => $fedIDs[1]], + ], + [ + 'label' => $fedIDs[2], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[2] + ], + 'extra' => ['federationId' => $fedIDs[2]], + ], + ] + ],// GS , Lookup + true, true + ], + [[ + 'search' => 'foo', + 'limit' => 10, + 'offset' => 0, + 'server' => 'https://lookup.example.io', + 'resultBody' => [ + ['federationId' => $fedIDs[0]], + ['federationId' => $fedIDs[1]], + ['federationId' => $fedIDs[2]], + ], + 'expectedResult' => [ + [ + 'label' => $fedIDs[0], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[0] + ], + 'extra' => ['federationId' => $fedIDs[0]], + ], + [ + 'label' => $fedIDs[1], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[1] + ], + 'extra' => ['federationId' => $fedIDs[1]], + ], + [ + 'label' => $fedIDs[2], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[2] + ], + 'extra' => ['federationId' => $fedIDs[2]], + ], + ] + ],// GS , Lookup + true, false + ], + [[ + 'search' => 'foo', + 'limit' => 10, + 'offset' => 0, + 'server' => 'https://lookup.example.io', + 'resultBody' => [ + ['federationId' => $fedIDs[0]], + ['federationId' => $fedIDs[1]], + ['federationId' => $fedIDs[2]], + ], + 'expectedResult' => [ + [ + 'label' => $fedIDs[0], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $fedIDs[0] + ], + 'extra' => ['federationId' => $fedIDs[0]], + ], + [ + 'label' => $fedIDs[1], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $fedIDs[1] + ], + 'extra' => ['federationId' => $fedIDs[1]], + ], + [ + 'label' => $fedIDs[2], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $fedIDs[2] + ], + 'extra' => ['federationId' => $fedIDs[2]], + ], + ] + ],// GS , Lookup + false, true + ], + [[ + 'search' => 'foo', + 'limit' => 10, + 'offset' => 0, + 'server' => 'https://lookup.example.io', + 'resultBody' => [ + ['federationId' => $fedIDs[0]], + ['federationId' => $fedIDs[1]], + ['federationId' => $fedIDs[2]], + ], + 'expectedResult' => [ + [ + 'label' => $fedIDs[0], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $fedIDs[0] + ], + 'extra' => ['federationId' => $fedIDs[0]], + ], + [ + 'label' => $fedIDs[1], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $fedIDs[1] + ], + 'extra' => ['federationId' => $fedIDs[1]], + ], + [ + 'label' => $fedIDs[2], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'shareWith' => $fedIDs[2] + ], + 'extra' => ['federationId' => $fedIDs[2]], + ], + ] + ],// GS , Lookup + false, false + ], + ]; + } + + public static function searchDataProvider(): array { + $fedIDs = [ + 'foo@enceladus.moon', + 'foobar@enceladus.moon', + 'foongus@enceladus.moon', + ]; + + return [ + // #0, standard search with results + [[ + 'search' => 'foo', + 'limit' => 10, + 'offset' => 0, + 'server' => 'https://lookup.example.io', + 'resultBody' => [ + ['federationId' => $fedIDs[0]], + ['federationId' => $fedIDs[1]], + ['federationId' => $fedIDs[2]], + ], + 'expectedResult' => [ + [ + 'label' => $fedIDs[0], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[0] + ], + 'extra' => ['federationId' => $fedIDs[0]], + ], + [ + 'label' => $fedIDs[1], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[1] + ], + 'extra' => ['federationId' => $fedIDs[1]], + ], + [ + 'label' => $fedIDs[2], + 'value' => [ + 'shareType' => IShare::TYPE_REMOTE, + 'globalScale' => true, + 'shareWith' => $fedIDs[2] + ], + 'extra' => ['federationId' => $fedIDs[2]], + ], + ] + ]], + // #1, search without results + [[ + 'search' => 'foo', + 'limit' => 10, + 'offset' => 0, + 'server' => 'https://lookup.example.io', + 'resultBody' => [], + 'expectedResult' => [], + ]], + ]; + } +} diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php new file mode 100644 index 00000000000..b38b961a512 --- /dev/null +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -0,0 +1,707 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Collaborators; + +use OC\Collaboration\Collaborators\MailPlugin; +use OC\Collaboration\Collaborators\SearchResult; +use OC\Federation\CloudIdManager; +use OC\KnownUser\KnownUserService; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Contacts\IManager; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Federation\ICloudIdManager; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Mail\IMailer; +use OCP\Share\IShare; +use Test\TestCase; + +class MailPluginTest extends TestCase { + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $contactsManager; + + /** @var ICloudIdManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $cloudIdManager; + + /** @var MailPlugin */ + protected $plugin; + + /** @var SearchResult */ + protected $searchResult; + + /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $groupManager; + + /** @var KnownUserService|\PHPUnit\Framework\MockObject\MockObject */ + protected $knownUserService; + + /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ + protected $userSession; + + /** @var IMailer|\PHPUnit\Framework\MockObject\MockObject */ + protected $mailer; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->contactsManager = $this->createMock(IManager::class); + $this->groupManager = $this->createMock(IGroupManager::class); + $this->knownUserService = $this->createMock(KnownUserService::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->mailer = $this->createMock(IMailer::class); + $this->cloudIdManager = new CloudIdManager( + $this->createMock(ICacheFactory::class), + $this->createMock(IEventDispatcher::class), + $this->contactsManager, + $this->createMock(IURLGenerator::class), + $this->createMock(IUserManager::class), + ); + + $this->searchResult = new SearchResult(); + } + + public function instantiatePlugin() { + $this->plugin = new MailPlugin( + $this->contactsManager, + $this->cloudIdManager, + $this->config, + $this->groupManager, + $this->knownUserService, + $this->userSession, + $this->mailer + ); + } + + /** + * + * @param string $searchTerm + * @param array $contacts + * @param bool $shareeEnumeration + * @param array $expected + * @param bool $reachedEnd + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmail')] + public function testSearch($searchTerm, $contacts, $shareeEnumeration, $expected, $exactIdMatch, $reachedEnd, $validEmail): void { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback( + function ($appName, $key, $default) use ($shareeEnumeration) { + if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') { + return $shareeEnumeration ? 'yes' : 'no'; + } + return $default; + } + ); + + $this->instantiatePlugin(); + + $currentUser = $this->createMock(IUser::class); + $currentUser->method('getUID') + ->willReturn('current'); + $this->userSession->method('getUser') + ->willReturn($currentUser); + + $this->mailer->method('validateMailAddress') + ->willReturn($validEmail); + + $this->contactsManager->expects($this->any()) + ->method('search') + ->willReturnCallback(function ($search, $searchAttributes) use ($searchTerm, $contacts) { + if ($search === $searchTerm) { + return $contacts; + } + return []; + }); + + $moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult); + $result = $this->searchResult->asArray(); + + $this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); + $this->assertEquals($expected, $result); + $this->assertSame($reachedEnd, $moreResults); + } + + public static function dataGetEmail(): array { + return [ + // data set 0 + ['test', [], true, ['emails' => [], 'exact' => ['emails' => []]], false, false, false], + // data set 1 + ['test', [], false, ['emails' => [], 'exact' => ['emails' => []]], false, false, false], + // data set 2 + [ + 'test@remote.com', + [], + true, + ['emails' => [], 'exact' => ['emails' => [['uuid' => 'test@remote.com', 'label' => 'test@remote.com', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + false, + false, + true, + ], + // data set 3 + [ // no valid email address + 'test@remote', + [], + true, + ['emails' => [], 'exact' => ['emails' => []]], + false, + false, + false, + ], + // data set 4 + [ + 'test@remote.com', + [], + false, + ['emails' => [], 'exact' => ['emails' => [['uuid' => 'test@remote.com', 'label' => 'test@remote.com', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + false, + false, + true, + ], + // data set 5 + [ + 'test', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@example.com', + ], + ], + ], + true, + ['emails' => [['uuid' => 'uid1', 'name' => 'User @ Localhost', 'type' => '', 'label' => 'User @ Localhost (username@example.com)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'username@example.com']]], 'exact' => ['emails' => []]], + false, + false, + false, + ], + // data set 6 + [ + 'test', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'isLocalSystemBook' => true, + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@localhost', + ], + ], + ], + false, + ['emails' => [], 'exact' => ['emails' => []]], + false, + false, + false, + ], + // data set 7 + [ + 'test@remote.com', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ example.com', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ example.com', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ example.com', + 'EMAIL' => [ + 'username@example.com', + ], + ], + ], + true, + ['emails' => [['uuid' => 'uid1', 'name' => 'User @ example.com', 'type' => '', 'label' => 'User @ example.com (username@example.com)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'username@example.com']]], 'exact' => ['emails' => [['label' => 'test@remote.com', 'uuid' => 'test@remote.com', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + false, + false, + true, + ], + // data set 8 + [ + 'test@remote.com', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'isLocalSystemBook' => true, + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@localhost', + ], + ], + ], + false, + ['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'uuid' => 'test@remote.com', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + false, + false, + true, + ], + // data set 9 + [ + 'username@example.com', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ example.com', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ example.com', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ example.com', + 'EMAIL' => [ + 'username@example.com', + ], + ], + ], + true, + ['emails' => [], 'exact' => ['emails' => [['name' => 'User @ example.com', 'uuid' => 'uid1', 'type' => '', 'label' => 'User @ example.com (username@example.com)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'username@example.com']]]]], + true, + false, + false, + ], + // data set 10 + [ + 'username@example.com', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User3 @ example.com', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ example.com', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ example.com', + 'EMAIL' => [ + 'username@example.com', + ], + ], + ], + false, + ['emails' => [], 'exact' => ['emails' => [['name' => 'User @ example.com', 'uuid' => 'uid1', 'type' => '', 'label' => 'User @ example.com (username@example.com)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'username@example.com']]]]], + true, + false, + false, + ], + // data set 11 + // contact with space + [ + 'user name@localhost', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User Name @ Localhost', + 'EMAIL' => [ + 'user name@localhost', + ], + ], + ], + false, + ['emails' => [], 'exact' => ['emails' => []]], + false, + false, + false, + ], + // data set 12 + // remote with space, no contact + [ + 'user space@remote.com', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'EMAIL' => [ + ], + ], + [ + 'isLocalSystemBook' => true, + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'EMAIL' => [ + 'username@localhost', + ], + ], + ], + false, + ['emails' => [], 'exact' => ['emails' => []]], + false, + false, + false, + ], + // data set 13 + // Local user found by email + [ + 'test@example.com', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + ] + ], + false, + ['users' => [], 'exact' => ['users' => [['uuid' => 'uid1', 'name' => 'User', 'label' => 'User (test@example.com)','value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'shareWithDisplayNameUnique' => 'test@example.com']]]], + true, + false, + true, + ], + // data set 14 + // Current local user found by email => no result + [ + 'test@example.com', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['current@localhost'], + 'isLocalSystemBook' => true, + ] + ], + true, + ['exact' => []], + false, + false, + true, + ], + // data set 15 + // Pagination and "more results" for user matches byyyyyyy emails + [ + 'test@example', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User1', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test1@localhost'], + 'isLocalSystemBook' => true, + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2', + 'EMAIL' => ['test@example.de'], + 'CLOUD' => ['test2@localhost'], + 'isLocalSystemBook' => true, + ], + [ + 'UID' => 'uid3', + 'FN' => 'User3', + 'EMAIL' => ['test@example.org'], + 'CLOUD' => ['test3@localhost'], + 'isLocalSystemBook' => true, + ], + [ + 'UID' => 'uid4', + 'FN' => 'User4', + 'EMAIL' => ['test@example.net'], + 'CLOUD' => ['test4@localhost'], + 'isLocalSystemBook' => true, + ], + ], + true, + ['users' => [ + ['uuid' => 'uid1', 'name' => 'User1', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'shareWithDisplayNameUnique' => 'test@example.com'], + ['uuid' => 'uid2', 'name' => 'User2', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'shareWithDisplayNameUnique' => 'test@example.de'], + ], 'emails' => [], 'exact' => ['users' => [], 'emails' => []]], + false, + true, + false, + ], + // data set 16 + // Pagination and "more results" for normal emails + [ + 'test@example', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User1', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test1@localhost'], + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2', + 'EMAIL' => ['test@example.de'], + 'CLOUD' => ['test2@localhost'], + ], + [ + 'UID' => 'uid3', + 'FN' => 'User3', + 'EMAIL' => ['test@example.org'], + 'CLOUD' => ['test3@localhost'], + ], + [ + 'UID' => 'uid4', + 'FN' => 'User4', + 'EMAIL' => ['test@example.net'], + 'CLOUD' => ['test4@localhost'], + ], + ], + true, + ['emails' => [ + ['uuid' => 'uid1', 'name' => 'User1', 'type' => '', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test@example.com']], + ['uuid' => 'uid2', 'name' => 'User2', 'type' => '', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test@example.de']], + ], 'exact' => ['emails' => []]], + false, + true, + false, + ], + // data set 17 + // multiple email addresses with type + [ + 'User Name', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User Name', + 'EMAIL' => [ + ['type' => 'HOME', 'value' => 'username@example.com'], + ['type' => 'WORK', 'value' => 'other@example.com'], + ], + ], + ], + false, + ['emails' => [ + ], 'exact' => ['emails' => [ + ['name' => 'User Name', 'uuid' => 'uid1', 'type' => 'HOME', 'label' => 'User Name (username@example.com)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'username@example.com']], + ['name' => 'User Name', 'uuid' => 'uid1', 'type' => 'WORK', 'label' => 'User Name (other@example.com)', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'other@example.com']] + ]]], + false, + false, + false, + ], + // data set 18 + // idn email + [ + 'test@lölölölölölölöl.com', + [], + true, + ['emails' => [], 'exact' => ['emails' => [['uuid' => 'test@lölölölölölölöl.com', 'label' => 'test@lölölölölölölöl.com', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test@lölölölölölölöl.com']]]]], + false, + false, + true, + ], + ]; + } + + /** + * + * @param string $searchTerm + * @param array $contacts + * @param array $expected + * @param bool $exactIdMatch + * @param bool $reachedEnd + * @param array groups + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetEmailGroupsOnly')] + public function testSearchGroupsOnly($searchTerm, $contacts, $expected, $exactIdMatch, $reachedEnd, $userToGroupMapping, $validEmail): void { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback( + function ($appName, $key, $default) { + if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') { + return 'yes'; + } elseif ($appName === 'core' && $key === 'shareapi_only_share_with_group_members') { + return 'yes'; + } + return $default; + } + ); + + $this->instantiatePlugin(); + + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject */ + $currentUser = $this->createMock('\OCP\IUser'); + + $currentUser->expects($this->any()) + ->method('getUID') + ->willReturn('currentUser'); + + $this->mailer->method('validateMailAddress') + ->willReturn($validEmail); + + $this->contactsManager->expects($this->any()) + ->method('search') + ->willReturnCallback(function ($search, $searchAttributes) use ($searchTerm, $contacts) { + if ($search === $searchTerm) { + return $contacts; + } + return []; + }); + + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($currentUser); + + $this->groupManager->expects($this->any()) + ->method('getUserGroupIds') + ->willReturnCallback(function (IUser $user) use ($userToGroupMapping) { + return $userToGroupMapping[$user->getUID()]; + }); + + $this->groupManager->expects($this->any()) + ->method('isInGroup') + ->willReturnCallback(function ($userId, $group) use ($userToGroupMapping) { + return in_array($group, $userToGroupMapping[$userId]); + }); + + $moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult); + $result = $this->searchResult->asArray(); + + $this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('emails'))); + $this->assertEquals($expected, $result); + $this->assertSame($reachedEnd, $moreResults); + } + + public static function dataGetEmailGroupsOnly(): array { + return [ + // The user `User` can share with the current user + [ + 'test', + [ + [ + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + 'UID' => 'User' + ] + ], + ['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => 0, 'shareWith' => 'test'],'shareWithDisplayNameUnique' => 'test@example.com',]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]], + false, + false, + [ + 'currentUser' => ['group1'], + 'User' => ['group1'] + ], + false, + ], + // The user `User` cannot share with the current user + [ + 'test', + [ + [ + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + 'UID' => 'User' + ] + ], + ['emails' => [], 'exact' => ['emails' => []]], + false, + false, + [ + 'currentUser' => ['group1'], + 'User' => ['group2'] + ], + false, + ], + // The user `User` cannot share with the current user, but there is an exact match on the e-mail address -> share by e-mail + [ + 'test@example.com', + [ + [ + 'FN' => 'User', + 'EMAIL' => ['test@example.com'], + 'CLOUD' => ['test@localhost'], + 'isLocalSystemBook' => true, + 'UID' => 'User' + ] + ], + ['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'uuid' => 'test@example.com', 'value' => ['shareType' => 4,'shareWith' => 'test@example.com']]]]], + false, + false, + [ + 'currentUser' => ['group1'], + 'User' => ['group2'] + ], + true, + ] + ]; + } +} diff --git a/tests/lib/Collaboration/Collaborators/RemotePluginTest.php b/tests/lib/Collaboration/Collaborators/RemotePluginTest.php new file mode 100644 index 00000000000..a9a5e05dfe4 --- /dev/null +++ b/tests/lib/Collaboration/Collaborators/RemotePluginTest.php @@ -0,0 +1,430 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Collaborators; + +use OC\Collaboration\Collaborators\RemotePlugin; +use OC\Collaboration\Collaborators\SearchResult; +use OC\Federation\CloudIdManager; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\Contacts\IManager; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Federation\ICloudIdManager; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share\IShare; +use Test\TestCase; + +class RemotePluginTest extends TestCase { + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $userManager; + + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + protected $config; + + /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $contactsManager; + + /** @var ICloudIdManager|\PHPUnit\Framework\MockObject\MockObject */ + protected $cloudIdManager; + + /** @var RemotePlugin */ + protected $plugin; + + /** @var SearchResult */ + protected $searchResult; + + protected function setUp(): void { + parent::setUp(); + + $this->userManager = $this->createMock(IUserManager::class); + $this->config = $this->createMock(IConfig::class); + $this->contactsManager = $this->createMock(IManager::class); + $this->cloudIdManager = new CloudIdManager( + $this->createMock(ICacheFactory::class), + $this->createMock(IEventDispatcher::class), + $this->contactsManager, + $this->createMock(IURLGenerator::class), + $this->createMock(IUserManager::class), + ); + $this->searchResult = new SearchResult(); + } + + public function instantiatePlugin() { + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('admin'); + $userSession = $this->createMock(IUserSession::class); + $userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $this->plugin = new RemotePlugin($this->contactsManager, $this->cloudIdManager, $this->config, $this->userManager, $userSession); + } + + /** + * + * @param string $searchTerm + * @param array $contacts + * @param bool $shareeEnumeration + * @param array $expected + * @param bool $exactIdMatch + * @param bool $reachedEnd + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetRemote')] + public function testSearch($searchTerm, array $contacts, $shareeEnumeration, array $expected, $exactIdMatch, $reachedEnd): void { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback( + function ($appName, $key, $default) use ($shareeEnumeration) { + if ($appName === 'core' && $key === 'shareapi_allow_share_dialog_user_enumeration') { + return $shareeEnumeration ? 'yes' : 'no'; + } + return $default; + } + ); + + $this->instantiatePlugin(); + + $this->contactsManager->expects($this->any()) + ->method('search') + ->willReturnCallback(function ($search, $searchAttributes) use ($searchTerm, $contacts) { + if ($search === $searchTerm) { + return $contacts; + } + return []; + }); + + $moreResults = $this->plugin->search($searchTerm, 2, 0, $this->searchResult); + $result = $this->searchResult->asArray(); + + $this->assertSame($exactIdMatch, $this->searchResult->hasExactIdMatch(new SearchResultType('remotes'))); + $this->assertEquals($expected, $result); + $this->assertSame($reachedEnd, $moreResults); + } + + /** + * + * @param string $remote + * @param string $expectedUser + * @param string $expectedUrl + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSplitUserRemote')] + public function testSplitUserRemote($remote, $expectedUser, $expectedUrl): void { + $this->instantiatePlugin(); + + $this->contactsManager->expects($this->any()) + ->method('search') + ->willReturn([]); + + [$remoteUser, $remoteUrl] = $this->plugin->splitUserRemote($remote); + $this->assertSame($expectedUser, $remoteUser); + $this->assertSame($expectedUrl, $remoteUrl); + } + + /** + * @param string $id + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSplitUserRemoteError')] + public function testSplitUserRemoteError($id): void { + $this->expectException(\Exception::class); + + $this->instantiatePlugin(); + $this->plugin->splitUserRemote($id); + } + + public static function dataGetRemote() { + return [ + ['test', [], true, ['remotes' => [], 'exact' => ['remotes' => []]], false, true], + ['test', [], false, ['remotes' => [], 'exact' => ['remotes' => []]], false, true], + [ + 'test@remote', + [], + true, + ['remotes' => [], 'exact' => ['remotes' => [['label' => 'test (remote)', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'test@remote', 'server' => 'remote'], 'uuid' => 'test', 'name' => 'test']]]], + false, + true, + ], + [ + 'test@remote', + [], + false, + ['remotes' => [], 'exact' => ['remotes' => [['label' => 'test (remote)', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'test@remote', 'server' => 'remote'], 'uuid' => 'test', 'name' => 'test']]]], + false, + true, + ], + [ + 'test', + [ + [ + 'UID' => 'uid', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + true, + ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid1', 'type' => '', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => []]], + false, + true, + ], + [ + 'test', + [ + [ + 'UID' => 'uid', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid', + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + false, + ['remotes' => [], 'exact' => ['remotes' => []]], + false, + true, + ], + [ + 'test@remote', + [ + [ + 'UID' => 'uid', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid', + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + true, + ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid', 'type' => '', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => [['label' => 'test (remote)', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'test@remote', 'server' => 'remote'], 'uuid' => 'test', 'name' => 'test']]]], + false, + true, + ], + [ + 'test@remote', + [ + [ + 'UID' => 'uid', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid', + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + false, + ['remotes' => [], 'exact' => ['remotes' => [['label' => 'test (remote)', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'test@remote', 'server' => 'remote'], 'uuid' => 'test', 'name' => 'test']]]], + false, + true, + ], + [ + 'username@localhost', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => '2', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + true, + ['remotes' => [], 'exact' => ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid1', 'type' => '', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]], + true, + true, + ], + [ + 'username@localhost', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + false, + ['remotes' => [], 'exact' => ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid1', 'type' => '', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]], + true, + true, + ], + // contact with space + [ + 'user name@localhost', + [ + [ + 'UID' => 'uid1', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid3', + 'FN' => 'User Name @ Localhost', + 'CLOUD' => [ + 'user name@localhost', + ], + ], + ], + false, + ['remotes' => [], 'exact' => ['remotes' => [['name' => 'User Name @ Localhost', 'label' => 'User Name @ Localhost (user name@localhost)', 'uuid' => 'uid3', 'type' => '', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'user name@localhost', 'server' => 'localhost']]]]], + true, + true, + ], + // remote with space, no contact + [ + 'user space@remote', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3 @ Localhost', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2 @ Localhost', + 'CLOUD' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User @ Localhost', + 'CLOUD' => [ + 'username@localhost', + ], + ], + ], + false, + ['remotes' => [], 'exact' => ['remotes' => [['label' => 'user space (remote)', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'user space@remote', 'server' => 'remote'], 'uuid' => 'user space', 'name' => 'user space']]]], + false, + true, + ], + ]; + } + + public static function dataTestSplitUserRemote(): array { + $userPrefix = ['user@name', 'username']; + $protocols = ['', 'http://', 'https://']; + $remotes = [ + 'localhost', + 'local.host', + 'dev.local.host', + 'dev.local.host/path', + 'dev.local.host/at@inpath', + '127.0.0.1', + '::1', + '::192.0.2.128', + '::192.0.2.128/at@inpath', + ]; + + $testCases = []; + foreach ($userPrefix as $user) { + foreach ($remotes as $remote) { + foreach ($protocols as $protocol) { + $baseUrl = $user . '@' . $protocol . $remote; + + if ($protocol === 'https://') { + // https:// protocol is not expected in the final result + $protocol = ''; + } + + $testCases[] = [$baseUrl, $user, $protocol . $remote]; + $testCases[] = [$baseUrl . '/', $user, $protocol . $remote]; + $testCases[] = [$baseUrl . '/index.php', $user, $protocol . $remote]; + $testCases[] = [$baseUrl . '/index.php/s/token', $user, $protocol . $remote]; + } + } + } + return $testCases; + } + + public static function dataTestSplitUserRemoteError(): array { + return [ + // Invalid path + ['user@'], + + // Invalid user + ['@server'], + ['us/er@server'], + ['us:er@server'], + + // Invalid splitting + ['user'], + [''], + ['us/erserver'], + ['us:erserver'], + ]; + } +} diff --git a/tests/lib/Collaboration/Collaborators/SearchResultTest.php b/tests/lib/Collaboration/Collaborators/SearchResultTest.php new file mode 100644 index 00000000000..687901c47a6 --- /dev/null +++ b/tests/lib/Collaboration/Collaborators/SearchResultTest.php @@ -0,0 +1,85 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Collaborators; + +use OC\Collaboration\Collaborators\Search; +use OC\Collaboration\Collaborators\SearchResult; +use OCP\Collaboration\Collaborators\ISearch; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IContainer; +use Test\TestCase; + +class SearchResultTest extends TestCase { + /** @var IContainer|\PHPUnit\Framework\MockObject\MockObject */ + protected $container; + /** @var ISearch */ + protected $search; + + protected function setUp(): void { + parent::setUp(); + + $this->container = $this->createMock(IContainer::class); + + $this->search = new Search($this->container); + } + + public static function dataAddResultSet(): array { + return [ + [[], ['exact' => []]], + [['users' => ['exact' => null, 'loose' => []]], ['exact' => ['users' => []], 'users' => []]], + [['groups' => ['exact' => null, 'loose' => ['l1']]], ['exact' => ['groups' => []], 'groups' => ['l1']]], + [['users' => ['exact' => ['e1'], 'loose' => []]], ['exact' => ['users' => ['e1']], 'users' => []]], + ]; + } + + /** + * @param array $toAdd + * @param array $expected + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataAddResultSet')] + public function testAddResultSet(array $toAdd, array $expected): void { + $result = new SearchResult(); + + foreach ($toAdd as $type => $results) { + $result->addResultSet(new SearchResultType($type), $results['loose'], $results['exact']); + } + + $this->assertEquals($expected, $result->asArray()); + } + + public static function dataHasResult(): array { + $result = ['value' => ['shareWith' => 'l1']]; + return [ + [[],'users', 'n1', false], + [['users' => ['exact' => null, 'loose' => [$result]]], 'users', 'l1', true], + [['users' => ['exact' => null, 'loose' => [$result]]], 'users', 'l2', false], + [['users' => ['exact' => null, 'loose' => [$result]]], 'groups', 'l1', false], + [['users' => ['exact' => [$result], 'loose' => []]], 'users', 'l1', true], + [['users' => ['exact' => [$result], 'loose' => []]], 'users', 'l2', false], + [['users' => ['exact' => [$result], 'loose' => []]], 'groups', 'l1', false], + + ]; + } + + /** + * @param array $toAdd + * @param string $type + * @param string $id + * @param bool $expected + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataHasResult')] + public function testHasResult(array $toAdd, $type, $id, $expected): void { + $result = new SearchResult(); + + foreach ($toAdd as $addType => $results) { + $result->addResultSet(new SearchResultType($addType), $results['loose'], $results['exact']); + } + + $this->assertSame($expected, $result->hasResult(new SearchResultType($type), $id)); + } +} diff --git a/tests/lib/Collaboration/Collaborators/SearchTest.php b/tests/lib/Collaboration/Collaborators/SearchTest.php new file mode 100644 index 00000000000..ade995ea526 --- /dev/null +++ b/tests/lib/Collaboration/Collaborators/SearchTest.php @@ -0,0 +1,253 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Collaborators; + +use OC\Collaboration\Collaborators\Search; +use OC\Collaboration\Collaborators\SearchResult; +use OCP\Collaboration\Collaborators\ISearch; +use OCP\Collaboration\Collaborators\ISearchPlugin; +use OCP\Collaboration\Collaborators\SearchResultType; +use OCP\IContainer; +use OCP\Share\IShare; +use Test\TestCase; + +class SearchTest extends TestCase { + /** @var IContainer|\PHPUnit\Framework\MockObject\MockObject */ + protected $container; + /** @var ISearch */ + protected $search; + + protected function setUp(): void { + parent::setUp(); + + $this->container = $this->createMock(IContainer::class); + + $this->search = new Search($this->container); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchSharees')] + public function testSearch( + string $searchTerm, + array $shareTypes, + int $page, + int $perPage, + array $mockedUserResult, + array $mockedGroupsResult, + array $mockedRemotesResult, + array $mockedMailResult, + array $expected, + bool $expectedMoreResults, + ): void { + $searchResult = new SearchResult(); + + $userPlugin = $this->createMock(ISearchPlugin::class); + $userPlugin->expects($this->any()) + ->method('search') + ->willReturnCallback(function () use ($searchResult, $mockedUserResult, $expectedMoreResults) { + $type = new SearchResultType('users'); + $searchResult->addResultSet($type, $mockedUserResult); + return $expectedMoreResults; + }); + + $groupPlugin = $this->createMock(ISearchPlugin::class); + $groupPlugin->expects($this->any()) + ->method('search') + ->willReturnCallback(function () use ($searchResult, $mockedGroupsResult, $expectedMoreResults) { + $type = new SearchResultType('groups'); + $searchResult->addResultSet($type, $mockedGroupsResult); + return $expectedMoreResults; + }); + + $remotePlugin = $this->createMock(ISearchPlugin::class); + $remotePlugin->expects($this->any()) + ->method('search') + ->willReturnCallback(function () use ($searchResult, $mockedRemotesResult, $expectedMoreResults) { + if ($mockedRemotesResult !== null) { + $type = new SearchResultType('remotes'); + $searchResult->addResultSet($type, $mockedRemotesResult['results'], $mockedRemotesResult['exact']); + if ($mockedRemotesResult['exactIdMatch'] === true) { + $searchResult->markExactIdMatch($type); + } + } + return $expectedMoreResults; + }); + + $mailPlugin = $this->createMock(ISearchPlugin::class); + $mailPlugin->expects($this->any()) + ->method('search') + ->willReturnCallback(function () use ($searchResult, $mockedMailResult, $expectedMoreResults) { + $type = new SearchResultType('emails'); + $searchResult->addResultSet($type, $mockedMailResult); + return $expectedMoreResults; + }); + + $this->container->expects($this->any()) + ->method('resolve') + ->willReturnCallback(function ($class) use ($searchResult, $userPlugin, $groupPlugin, $remotePlugin, $mailPlugin) { + if ($class === SearchResult::class) { + return $searchResult; + } elseif ($class === $userPlugin) { + return $userPlugin; + } elseif ($class === $groupPlugin) { + return $groupPlugin; + } elseif ($class === $remotePlugin) { + return $remotePlugin; + } elseif ($class === $mailPlugin) { + return $mailPlugin; + } + return null; + }); + + $this->search->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => $userPlugin]); + $this->search->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => $groupPlugin]); + $this->search->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => $remotePlugin]); + $this->search->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => $mailPlugin]); + + [$results, $moreResults] = $this->search->search($searchTerm, $shareTypes, false, $perPage, $perPage * ($page - 1)); + + $this->assertEquals($expected, $results); + $this->assertSame($expectedMoreResults, $moreResults); + } + + public static function dataSearchSharees(): array { + return [ + // #0 + [ + 'test', [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_REMOTE], 1, 2, [], [], ['results' => [], 'exact' => [], 'exactIdMatch' => false], + [], + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [], + 'groups' => [], + 'remotes' => [], + ], + false + ], + // #1 + [ + 'test', [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_REMOTE], 1, 2, [], [], ['results' => [], 'exact' => [], 'exactIdMatch' => false], + [], + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [], + 'groups' => [], + 'remotes' => [], + ], + false + ], + // #2 + [ + 'test', [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_REMOTE], 1, 2, [ + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], [ + ['label' => 'testgroup1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'testgroup1']], + ], [ + 'results' => [['label' => 'testz@remote', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'testz@remote']]], 'exact' => [], 'exactIdMatch' => false, + ], + [], + [ + 'exact' => ['users' => [], 'groups' => [], 'remotes' => []], + 'users' => [ + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], + 'groups' => [ + ['label' => 'testgroup1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'testgroup1']], + ], + 'remotes' => [ + ['label' => 'testz@remote', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'testz@remote']], + ], + ], true, + ], + // #3 No groups requested + [ + 'test', [IShare::TYPE_USER, IShare::TYPE_REMOTE], 1, 2, [ + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], [], [ + 'results' => [['label' => 'testz@remote', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'testz@remote']]], 'exact' => [], 'exactIdMatch' => false + ], + [], + [ + 'exact' => ['users' => [], 'remotes' => []], + 'users' => [ + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], + 'remotes' => [ + ['label' => 'testz@remote', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'testz@remote']], + ], + ], false, + ], + // #4 Share type restricted to user - Only one user + [ + 'test', [IShare::TYPE_USER], 1, 2, [ + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], [], [], [], + [ + 'exact' => ['users' => []], + 'users' => [ + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], + ], false, + ], + // #5 Share type restricted to user - Multipage result + [ + 'test', [IShare::TYPE_USER], 1, 2, [ + ['label' => 'test 1', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ['label' => 'test 2', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2']], + ], [], [], [], + [ + 'exact' => ['users' => []], + 'users' => [ + ['label' => 'test 1', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ['label' => 'test 2', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2']], + ], + ], true, + ], + // #6 Mail shares filtered out in favor of remote shares + [ + 'test', // search term + [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL], // plugins + 1, // page + 10, // per page + [ // user result + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], + [ // group result + ['label' => 'testgroup1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'testgroup1']], + ], + [ // remote result + 'results' => [ + ['label' => 'testz@remote.tld', 'uuid' => 'f3d78140-abcc-46df-b58d-c7cc1176aadf','value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'testz@remote.tld']] + ], + 'exact' => [], + 'exactIdMatch' => false, + ], + [ // mail result + ['label' => 'test Two', 'uuid' => 'b2321e9e-31af-43ac-a406-583fb26d1964', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test2@remote.tld']], + ['label' => 'test One', 'uuid' => 'f3d78140-abcc-46df-b58d-c7cc1176aadf', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'testz@remote.tld']], + ], + [ // expected result + 'exact' => ['users' => [], 'groups' => [], 'remotes' => [], 'emails' => []], + 'users' => [ + ['label' => 'test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1']], + ], + 'groups' => [ + ['label' => 'testgroup1', 'value' => ['shareType' => IShare::TYPE_GROUP, 'shareWith' => 'testgroup1']], + ], + 'remotes' => [ + ['label' => 'testz@remote.tld', 'uuid' => 'f3d78140-abcc-46df-b58d-c7cc1176aadf', 'value' => ['shareType' => IShare::TYPE_REMOTE, 'shareWith' => 'testz@remote.tld']], + ], + 'emails' => [ + // one passes, another is filtered out + ['label' => 'test Two', 'uuid' => 'b2321e9e-31af-43ac-a406-583fb26d1964', 'value' => ['shareType' => IShare::TYPE_EMAIL, 'shareWith' => 'test2@remote.tld']] + ] + ], + false, // expected more results indicator + ], + ]; + } +} diff --git a/tests/lib/Collaboration/Collaborators/UserPluginTest.php b/tests/lib/Collaboration/Collaborators/UserPluginTest.php new file mode 100644 index 00000000000..cb4949fb86d --- /dev/null +++ b/tests/lib/Collaboration/Collaborators/UserPluginTest.php @@ -0,0 +1,810 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Collaborators; + +use OC\Collaboration\Collaborators\SearchResult; +use OC\Collaboration\Collaborators\UserPlugin; +use OC\KnownUser\KnownUserService; +use OCP\Collaboration\Collaborators\ISearchResult; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share\IShare; +use OCP\UserStatus\IManager as IUserStatusManager; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class UserPluginTest extends TestCase { + /** @var IConfig|MockObject */ + protected $config; + + /** @var IUserManager|MockObject */ + protected $userManager; + + /** @var IGroupManager|MockObject */ + protected $groupManager; + + /** @var IUserSession|MockObject */ + protected $session; + + /** @var KnownUserService|MockObject */ + protected $knownUserService; + + /** @var IUserStatusManager|MockObject */ + protected $userStatusManager; + + /** @var UserPlugin */ + protected $plugin; + + /** @var ISearchResult */ + protected $searchResult; + + protected int $limit = 2; + + protected int $offset = 0; + + /** @var IUser|MockObject */ + protected $user; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + + $this->userManager = $this->createMock(IUserManager::class); + + $this->groupManager = $this->createMock(IGroupManager::class); + + $this->session = $this->createMock(IUserSession::class); + + $this->knownUserService = $this->createMock(KnownUserService::class); + + $this->userStatusManager = $this->createMock(IUserStatusManager::class); + + $this->searchResult = new SearchResult(); + + $this->user = $this->getUserMock('admin', 'Administrator'); + } + + public function instantiatePlugin() { + // cannot be done within setUp, because dependent mocks needs to be set + // up with configuration etc. first + $this->plugin = new UserPlugin( + $this->config, + $this->userManager, + $this->groupManager, + $this->session, + $this->knownUserService, + $this->userStatusManager + ); + } + + public function mockConfig($mockedSettings) { + $this->config->expects($this->any()) + ->method('getAppValue') + ->willReturnCallback( + function ($appName, $key, $default) use ($mockedSettings) { + return $mockedSettings[$appName][$key] ?? $default; + } + ); + } + + public function getUserMock($uid, $displayName, $enabled = true, $groups = []) { + $user = $this->createMock(IUser::class); + + $user->expects($this->any()) + ->method('getUID') + ->willReturn($uid); + + $user->expects($this->any()) + ->method('getDisplayName') + ->willReturn($displayName); + + $user->expects($this->any()) + ->method('isEnabled') + ->willReturn($enabled); + + return $user; + } + + public function getGroupMock($gid) { + $group = $this->createMock(IGroup::class); + + $group->expects($this->any()) + ->method('getGID') + ->willReturn($gid); + + return $group; + } + + public function dataGetUsers(): array { + return [ + ['test', false, true, [], [], [], [], true, false], + ['test', false, false, [], [], [], [], true, false], + ['test', true, true, [], [], [], [], true, false], + ['test', true, false, [], [], [], [], true, false], + [ + 'test', false, true, [], [], + [ + ['label' => 'Test', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test'], + ], [], true, $this->getUserMock('test', 'Test'), + ], + [ + 'test', false, false, [], [], + [ + ['label' => 'Test', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test'], + ], [], true, $this->getUserMock('test', 'Test'), + ], + [ + 'test', true, true, [], [], + [], [], true, $this->getUserMock('test', 'Test'), + ], + [ + 'test', true, false, [], [], + [], [], true, $this->getUserMock('test', 'Test'), + ], + [ + 'test', true, true, ['test-group'], [['test-group', 'test', 2, 0, []]], + [ + ['label' => 'Test', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test'], + ], [], true, $this->getUserMock('test', 'Test'), + ], + [ + 'test', true, false, ['test-group'], [['test-group', 'test', 2, 0, []]], + [ + ['label' => 'Test', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test'], + ], [], true, $this->getUserMock('test', 'Test'), + ], + [ + 'test', + false, + true, + [], + [ + $this->getUserMock('test1', 'Test One'), + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test1'], + ], + true, + false, + ], + [ + 'test', + false, + false, + [], + [ + $this->getUserMock('test1', 'Test One'), + ], + [], + [], + true, + false, + ], + [ + 'test', + false, + true, + [], + [ + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test1'], + ['label' => 'Test Two', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test2'], + ], + false, + false, + ], + [ + 'test', + false, + false, + [], + [ + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [], + [], + true, + false, + ], + [ + 'test', + false, + true, + [], + [ + $this->getUserMock('test0', 'Test'), + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [ + ['label' => 'Test', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test0'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test0'], + ], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test1'], + ['label' => 'Test Two', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test2'], + ], + false, + false, + ], + [ + 'test', + false, + true, + [], + [ + $this->getUserMock('test0', 'Test'), + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [ + ['label' => 'Test', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test0'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test0'], + ], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test1'], + ['label' => 'Test Two', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test2'], + ], + false, + false, + [], + true, + ], + [ + 'test', + false, + false, + [], + [ + $this->getUserMock('test0', 'Test'), + $this->getUserMock('test1', 'Test One'), + $this->getUserMock('test2', 'Test Two'), + ], + [ + ['label' => 'Test', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test0'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test0'], + ], + [], + true, + false, + ], + [ + 'test', + true, + true, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, ['test1' => 'Test One']], + ['xyz', 'test', 2, 0, []], + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test1'], + ], + true, + false, + [['test1', $this->getUserMock('test1', 'Test One')]], + ], + [ + 'test', + true, + false, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, ['test1' => 'Test One']], + ['xyz', 'test', 2, 0, []], + ], + [], + [], + true, + false, + [['test1', $this->getUserMock('test1', 'Test One')]], + ], + [ + 'test', + true, + true, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ['xyz', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ], + [], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test1'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test1'], + ['label' => 'Test Two', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test2'], + ], + true, + false, + [ + ['test1', $this->getUserMock('test1', 'Test One')], + ['test2', $this->getUserMock('test2', 'Test Two')], + ], + ], + [ + 'test', + true, + false, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ['xyz', 'test', 2, 0, [ + 'test1' => 'Test One', + 'test2' => 'Test Two', + ]], + ], + [], + [], + true, + false, + [ + ['test1', $this->getUserMock('test1', 'Test One')], + ['test2', $this->getUserMock('test2', 'Test Two')], + ], + ], + [ + 'test', + true, + true, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test' => 'Test One', + ]], + ['xyz', 'test', 2, 0, [ + 'test2' => 'Test Two', + ]], + ], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test'], + ], + [ + ['label' => 'Test Two', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test2'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test2'], + ], + false, + false, + [ + ['test', $this->getUserMock('test', 'Test One')], + ['test2', $this->getUserMock('test2', 'Test Two')], + ], + ], + [ + 'test', + true, + false, + ['abc', 'xyz'], + [ + ['abc', 'test', 2, 0, [ + 'test' => 'Test One', + ]], + ['xyz', 'test', 2, 0, [ + 'test2' => 'Test Two', + ]], + ], + [ + ['label' => 'Test One', 'value' => ['shareType' => IShare::TYPE_USER, 'shareWith' => 'test'], 'icon' => 'icon-user', 'subline' => null, 'status' => [], 'shareWithDisplayNameUnique' => 'test'], + ], + [], + true, + false, + [ + ['test', $this->getUserMock('test', 'Test One')], + ['test2', $this->getUserMock('test2', 'Test Two')], + ], + ], + ]; + } + + /** + * + * @param string $searchTerm + * @param bool $shareWithGroupOnly + * @param bool $shareeEnumeration + * @param array $groupResponse + * @param array $userResponse + * @param array $exactExpected + * @param array $expected + * @param bool $reachedEnd + * @param bool|IUser $singleUser + * @param array $users + */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetUsers')] + public function testSearch( + $searchTerm, + $shareWithGroupOnly, + $shareeEnumeration, + array $groupResponse, + array $userResponse, + array $exactExpected, + array $expected, + $reachedEnd, + $singleUser, + array $users = [], + $shareeEnumerationPhone = false, + ): void { + $this->mockConfig(['core' => [ + 'shareapi_only_share_with_group_members' => $shareWithGroupOnly ? 'yes' : 'no', + 'shareapi_allow_share_dialog_user_enumeration' => $shareeEnumeration? 'yes' : 'no', + 'shareapi_restrict_user_enumeration_to_group' => false ? 'yes' : 'no', + 'shareapi_restrict_user_enumeration_to_phone' => $shareeEnumerationPhone ? 'yes' : 'no', + ]]); + + $this->instantiatePlugin(); + + $this->session->expects($this->any()) + ->method('getUser') + ->willReturn($this->user); + + if (!$shareWithGroupOnly) { + if ($shareeEnumerationPhone) { + $this->userManager->expects($this->once()) + ->method('searchKnownUsersByDisplayName') + ->with($this->user->getUID(), $searchTerm, $this->limit, $this->offset) + ->willReturn($userResponse); + + $this->knownUserService->method('isKnownToUser') + ->willReturnMap([ + [$this->user->getUID(), 'test0', true], + [$this->user->getUID(), 'test1', true], + [$this->user->getUID(), 'test2', true], + ]); + } else { + $this->userManager->expects($this->once()) + ->method('searchDisplayName') + ->with($searchTerm, $this->limit, $this->offset) + ->willReturn($userResponse); + } + } else { + $this->groupManager->method('getUserGroupIds') + ->with($this->user) + ->willReturn($groupResponse); + + if ($singleUser !== false) { + $this->groupManager->method('getUserGroupIds') + ->with($singleUser) + ->willReturn($groupResponse); + } + + $this->groupManager->method('displayNamesInGroup') + ->willReturnMap($userResponse); + } + + if ($singleUser !== false) { + $users[] = [$searchTerm, $singleUser]; + } + + if (!empty($users)) { + $this->userManager->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap($users); + } + + $moreResults = $this->plugin->search($searchTerm, $this->limit, $this->offset, $this->searchResult); + $result = $this->searchResult->asArray(); + + $this->assertEquals($exactExpected, $result['exact']['users']); + $this->assertEquals($expected, $result['users']); + $this->assertSame($reachedEnd, $moreResults); + } + + public static function takeOutCurrentUserProvider(): array { + $inputUsers = [ + 'alice' => 'Alice', + 'bob' => 'Bob', + 'carol' => 'Carol', + ]; + return [ + [ + $inputUsers, + ['alice', 'carol'], + 'bob', + ], + [ + $inputUsers, + ['alice', 'bob', 'carol'], + 'dave', + ], + [ + $inputUsers, + ['alice', 'bob', 'carol'], + null, + ], + ]; + } + + /** + * @param array $users + * @param array $expectedUIDs + * @param $currentUserId + */ + #[\PHPUnit\Framework\Attributes\DataProvider('takeOutCurrentUserProvider')] + public function testTakeOutCurrentUser(array $users, array $expectedUIDs, $currentUserId): void { + $this->instantiatePlugin(); + + $this->session->expects($this->once()) + ->method('getUser') + ->willReturnCallback(function () use ($currentUserId) { + if ($currentUserId !== null) { + return $this->getUserMock($currentUserId, $currentUserId); + } + return null; + }); + + $this->plugin->takeOutCurrentUser($users); + $this->assertSame($expectedUIDs, array_keys($users)); + } + + public static function dataSearchEnumeration(): array { + return [ + [ + 'test', + ['groupA'], + [ + ['uid' => 'test1', 'groups' => ['groupA']], + ['uid' => 'test2', 'groups' => ['groupB']], + ], + ['exact' => [], 'wide' => ['test1']], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + [ + 'test', + ['groupA'], + [ + ['uid' => 'test1', 'displayName' => 'Test user 1', 'groups' => ['groupA']], + ['uid' => 'test2', 'displayName' => 'Test user 2', 'groups' => ['groupA']], + ], + ['exact' => [], 'wide' => []], + ['core' => ['shareapi_allow_share_dialog_user_enumeration' => 'no']], + ], + [ + 'test1', + ['groupA'], + [ + ['uid' => 'test1', 'displayName' => 'Test user 1', 'groups' => ['groupA']], + ['uid' => 'test2', 'displayName' => 'Test user 2', 'groups' => ['groupA']], + ], + ['exact' => ['test1'], 'wide' => []], + ['core' => ['shareapi_allow_share_dialog_user_enumeration' => 'no']], + ], + [ + 'test1', + ['groupA'], + [ + ['uid' => 'test1', 'displayName' => 'Test user 1', 'groups' => ['groupA']], + ['uid' => 'test2', 'displayName' => 'Test user 2', 'groups' => ['groupA']], + ], + ['exact' => [], 'wide' => []], + [ + 'core' => [ + 'shareapi_allow_share_dialog_user_enumeration' => 'no', + 'shareapi_restrict_user_enumeration_full_match_userid' => 'no', + ], + ] + ], + [ + 'Test user 1', + ['groupA'], + [ + ['uid' => 'test1', 'displayName' => 'Test user 1', 'groups' => ['groupA']], + ['uid' => 'test2', 'displayName' => 'Test user 2', 'groups' => ['groupA']], + ], + ['exact' => ['test1'], 'wide' => []], + [ + 'core' => [ + 'shareapi_allow_share_dialog_user_enumeration' => 'no', + 'shareapi_restrict_user_enumeration_full_match_userid' => 'no', + ], + ] + ], + [ + 'Test user 1', + ['groupA'], + [ + ['uid' => 'test1', 'displayName' => 'Test user 1 (Second displayName for user 1)', 'groups' => ['groupA']], + ['uid' => 'test2', 'displayName' => 'Test user 2 (Second displayName for user 2)', 'groups' => ['groupA']], + ], + ['exact' => [], 'wide' => []], + ['core' => ['shareapi_allow_share_dialog_user_enumeration' => 'no'], + ] + ], + [ + 'Test user 1', + ['groupA'], + [ + ['uid' => 'test1', 'displayName' => 'Test user 1 (Second displayName for user 1)', 'groups' => ['groupA']], + ['uid' => 'test2', 'displayName' => 'Test user 2 (Second displayName for user 2)', 'groups' => ['groupA']], + ], + ['exact' => ['test1'], 'wide' => []], + [ + 'core' => [ + 'shareapi_allow_share_dialog_user_enumeration' => 'no', + 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn' => 'yes', + ], + ] + ], + [ + 'test1', + ['groupA'], + [ + ['uid' => 'test1', 'groups' => ['groupA']], + ['uid' => 'test2', 'groups' => ['groupB']], + ], + ['exact' => ['test1'], 'wide' => []], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + [ + 'test', + ['groupA'], + [ + ['uid' => 'test1', 'groups' => ['groupA']], + ['uid' => 'test2', 'groups' => ['groupB', 'groupA']], + ], + ['exact' => [], 'wide' => ['test1', 'test2']], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + [ + 'test', + ['groupA'], + [ + ['uid' => 'test1', 'groups' => ['groupA', 'groupC']], + ['uid' => 'test2', 'groups' => ['groupB', 'groupA']], + ], + ['exact' => [], 'wide' => ['test1', 'test2']], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + [ + 'test', + ['groupC', 'groupB'], + [ + ['uid' => 'test1', 'groups' => ['groupA', 'groupC']], + ['uid' => 'test2', 'groups' => ['groupB', 'groupA']], + ], + ['exact' => [], 'wide' => ['test1', 'test2']], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + [ + 'test', + [], + [ + ['uid' => 'test1', 'groups' => ['groupA']], + ['uid' => 'test2', 'groups' => ['groupB', 'groupA']], + ], + ['exact' => [], 'wide' => []], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + [ + 'test', + ['groupC', 'groupB'], + [ + ['uid' => 'test1', 'groups' => []], + ['uid' => 'test2', 'groups' => []], + ], + ['exact' => [], 'wide' => []], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + [ + 'test', + ['groupC', 'groupB'], + [ + ['uid' => 'test1', 'groups' => []], + ['uid' => 'test2', 'groups' => []], + ], + ['exact' => [], 'wide' => []], + ['core' => ['shareapi_restrict_user_enumeration_to_group' => 'yes']], + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchEnumeration')] + public function testSearchEnumerationLimit($search, $userGroups, $matchingUsers, $result, $mockedSettings): void { + $this->mockConfig($mockedSettings); + + $userResults = []; + foreach ($matchingUsers as $user) { + $userResults[$user['uid']] = $user['uid']; + } + + $usersById = []; + foreach ($matchingUsers as $user) { + $usersById[$user['uid']] = $user; + } + + $mappedResultExact = array_map(function ($user) use ($usersById, $search) { + return [ + 'label' => $search === $user ? $user : $usersById[$user]['displayName'], + 'value' => ['shareType' => 0, 'shareWith' => $user], + 'icon' => 'icon-user', + 'subline' => null, + 'status' => [], + 'shareWithDisplayNameUnique' => $user, + ]; + }, $result['exact']); + $mappedResultWide = array_map(function ($user) { + return [ + 'label' => $user, + 'value' => ['shareType' => 0, 'shareWith' => $user], + 'icon' => 'icon-user', + 'subline' => null, + 'status' => [], + 'shareWithDisplayNameUnique' => $user, + ]; + }, $result['wide']); + + $this->userManager + ->method('get') + ->willReturnCallback(function ($userId) use ($userResults) { + if (isset($userResults[$userId])) { + return $this->getUserMock($userId, $userId); + } + return null; + }); + $this->userManager + ->method('searchDisplayName') + ->willReturnCallback(function ($search) use ($matchingUsers) { + $users = array_filter( + $matchingUsers, + fn ($user) => str_contains(strtolower($user['displayName']), strtolower($search)) + ); + return array_map( + fn ($user) => $this->getUserMock($user['uid'], $user['displayName']), + $users); + }); + + $this->groupManager->method('displayNamesInGroup') + ->willReturn($userResults); + + + $this->session->expects($this->any()) + ->method('getUser') + ->willReturn($this->getUserMock('test', 'foo')); + $this->groupManager->expects($this->any()) + ->method('getUserGroupIds') + ->willReturnCallback(function ($user) use ($matchingUsers, $userGroups) { + static $firstCall = true; + if ($firstCall) { + $firstCall = false; + // current user + return $userGroups; + } + $neededObject = array_filter( + $matchingUsers, + function ($e) use ($user) { + return $user->getUID() === $e['uid']; + } + ); + if (count($neededObject) > 0) { + return array_shift($neededObject)['groups']; + } + return []; + }); + + $this->instantiatePlugin(); + $this->plugin->search($search, $this->limit, $this->offset, $this->searchResult); + $result = $this->searchResult->asArray(); + + $this->assertEquals($mappedResultExact, $result['exact']['users']); + $this->assertEquals($mappedResultWide, $result['users']); + } +} diff --git a/tests/lib/Collaboration/Resources/ManagerTest.php b/tests/lib/Collaboration/Resources/ManagerTest.php new file mode 100644 index 00000000000..0e4e42458e2 --- /dev/null +++ b/tests/lib/Collaboration/Resources/ManagerTest.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Resources; + +use OC\Collaboration\Resources\Manager; +use OCP\Collaboration\Resources\IManager; +use OCP\Collaboration\Resources\IProviderManager; +use OCP\IDBConnection; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class ManagerTest extends TestCase { + + protected LoggerInterface&MockObject $logger; + protected IProviderManager&MockObject $providerManager; + protected IManager $manager; + + protected function setUp(): void { + parent::setUp(); + + $this->logger = $this->createMock(LoggerInterface::class); + $this->providerManager = $this->createMock(IProviderManager::class); + + /** @var IDBConnection $connection */ + $connection = $this->createMock(IDBConnection::class); + $this->manager = new Manager($connection, $this->providerManager, $this->logger); + } + + public function testRegisterResourceProvider(): void { + $this->logger->expects($this->once()) + ->method('debug') + ->with($this->equalTo('\OC\Collaboration\Resources\Manager::registerResourceProvider is deprecated'), $this->equalTo(['provider' => 'AwesomeResourceProvider'])); + $this->providerManager->expects($this->once()) + ->method('registerResourceProvider') + ->with($this->equalTo('AwesomeResourceProvider')); + + $this->manager->registerResourceProvider('AwesomeResourceProvider'); + } +} diff --git a/tests/lib/Collaboration/Resources/ProviderManagerTest.php b/tests/lib/Collaboration/Resources/ProviderManagerTest.php new file mode 100644 index 00000000000..b063d89f06e --- /dev/null +++ b/tests/lib/Collaboration/Resources/ProviderManagerTest.php @@ -0,0 +1,98 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace Test\Collaboration\Resources; + +use OC\Collaboration\Resources\ProviderManager; +use OCA\Files\Collaboration\Resources\ResourceProvider; +use OCP\AppFramework\QueryException; +use OCP\Collaboration\Resources\IProviderManager; +use OCP\IServerContainer; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class ProviderManagerTest extends TestCase { + /** @var IServerContainer */ + protected $serverContainer; + /** @var LoggerInterface */ + protected $logger; + /** @var IProviderManager */ + protected $providerManager; + + protected function setUp(): void { + parent::setUp(); + + $this->serverContainer = $this->createMock(IServerContainer::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->providerManager = new class($this->serverContainer, $this->logger) extends ProviderManager { + public function countProviders(): int { + return count($this->providers); + } + }; + } + + public function testRegisterResourceProvider(): void { + $this->providerManager->registerResourceProvider('AwesomeResourceProvider'); + $this->assertSame(1, $this->providerManager->countProviders()); + } + + public function testGetResourceProvidersNoProvider(): void { + $this->assertCount(0, $this->providerManager->getResourceProviders()); + } + + public function testGetResourceProvidersValidProvider(): void { + $this->serverContainer->expects($this->once()) + ->method('query') + ->with($this->equalTo(ResourceProvider::class)) + ->willReturn($this->createMock(ResourceProvider::class)); + + $this->providerManager->registerResourceProvider(ResourceProvider::class); + $resourceProviders = $this->providerManager->getResourceProviders(); + + $this->assertCount(1, $resourceProviders); + $this->assertInstanceOf(ResourceProvider::class, $resourceProviders[0]); + } + + public function testGetResourceProvidersInvalidProvider(): void { + $this->serverContainer->expects($this->once()) + ->method('query') + ->with($this->equalTo('InvalidResourceProvider')) + ->willThrowException(new QueryException('A meaningful error message')); + + $this->logger->expects($this->once()) + ->method('error'); + + $this->providerManager->registerResourceProvider('InvalidResourceProvider'); + $resourceProviders = $this->providerManager->getResourceProviders(); + + $this->assertCount(0, $resourceProviders); + } + + public function testGetResourceProvidersValidAndInvalidProvider(): void { + $this->serverContainer->expects($this->exactly(2)) + ->method('query') + ->willReturnCallback(function (string $service) { + if ($service === 'InvalidResourceProvider') { + throw new QueryException('A meaningful error message'); + } + if ($service === ResourceProvider::class) { + return $this->createMock(ResourceProvider::class); + } + }); + + $this->logger->expects($this->once()) + ->method('error'); + + $this->providerManager->registerResourceProvider('InvalidResourceProvider'); + $this->providerManager->registerResourceProvider(ResourceProvider::class); + $resourceProviders = $this->providerManager->getResourceProviders(); + + $this->assertCount(1, $resourceProviders); + } +} |