diff --git a/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php b/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php index 8e12971565f8..38ac9225193d 100644 --- a/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php @@ -24,6 +24,7 @@ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; +use OCA\DAV\Files\Xml\FilterRequest; use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\ServerPlugin; @@ -139,6 +140,8 @@ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; + $server->xml->elementMap[self::REPORT_NAME] = FilterRequest::class; + $this->server = $server; $this->server->on('report', [$this, 'onReport']); } @@ -159,7 +162,7 @@ public function getSupportedReportSet($uri) { * REPORT operations to look for files * * @param string $reportName - * @param $report + * @param mixed $report * @param string $uri * @return bool * @throws BadRequest @@ -167,50 +170,44 @@ public function getSupportedReportSet($uri) { * @internal param $ [] $report */ public function onReport($reportName, $report, $uri) { + $reportTargetNode = $this->server->tree->getNodeForPath($uri); if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) { return; } - $ns = '{' . $this::NS_OWNCLOUD . '}'; - $requestedProps = []; - $filterRules = []; - - // parse report properties and gather filter info - foreach ($report as $reportProps) { - $name = $reportProps['name']; - if ($name === $ns . 'filter-rules') { - $filterRules = $reportProps['value']; - } else if ($name === '{DAV:}prop') { - // propfind properties - foreach ($reportProps['value'] as $propVal) { - $requestedProps[] = $propVal['name']; - } + $requestedProps = $report->properties; + $filterRules = $report->filters; + + // "systemtag" is always an array of tags, favorite a string/int/null + if (empty($filterRules['systemtag']) && is_null($filterRules['favorite'])) { + // FIXME: search currently not possible because results are missing properties! + throw new BadRequest('No filter criteria specified'); + } else { + if (isset($report->search['pattern'])) { + // TODO: implement this at some point... + throw new BadRequest('Search pattern cannot be combined with filter'); } - } - if (empty($filterRules)) { - // an empty filter would return all existing files which would be slow - throw new BadRequest('Missing filter-rule block in request'); - } + // gather all file ids matching filter + try { + $resultFileIds = $this->processFilterRules($filterRules); + } catch (TagNotFoundException $e) { + throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e); + } - // gather all file ids matching filter - try { - $resultFileIds = $this->processFilterRules($filterRules); - } catch (TagNotFoundException $e) { - throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e); - } + // pre-slice the results if needed for pagination to not waste + // time resolving nodes that will not be returned anyway + $resultFileIds = $this->slice($resultFileIds, $report); - // find sabre nodes by file id, restricted to the root node path - $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds); + // find sabre nodes by file id, restricted to the root node path + $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds); + } $filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath()); - $responses = $this->prepareResponses($filesUri, $requestedProps, $results); + $results = $this->prepareResponses($filesUri, $requestedProps, $results); - $xml = $this->server->xml->write( - '{DAV:}multistatus', - new MultiStatus($responses) - ); + $xml = $this->server->generateMultiStatus($results); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); @@ -219,6 +216,15 @@ public function onReport($reportName, $report, $uri) { return false; } + private function slice($results, $report) { + if (!is_null($report->search)) { + $length = $report->search['limit']; + $offset = $report->search['offset']; + $results = array_slice($results, $offset, $length); + } + return $results; + } + /** * Returns the base uri of the files root by removing * the subpath from the URI @@ -252,18 +258,9 @@ private function getFilesBaseUri($uri, $subPath) { * @throws TagNotFoundException whenever a tag was not found */ protected function processFilterRules($filterRules) { - $ns = '{' . $this::NS_OWNCLOUD . '}'; $resultFileIds = null; - $systemTagIds = []; - $favoriteFilter = null; - foreach ($filterRules as $filterRule) { - if ($filterRule['name'] === $ns . 'systemtag') { - $systemTagIds[] = $filterRule['value']; - } - if ($filterRule['name'] === $ns . 'favorite') { - $favoriteFilter = true; - } - } + $systemTagIds = $filterRules['systemtag']; + $favoriteFilter = $filterRules['favorite']; if ($favoriteFilter !== null) { $resultFileIds = $this->fileTagger->load('files')->getFavorites(); @@ -337,7 +334,7 @@ private function getSystemTagFileIds($systemTagIds) { * @return Response[] */ public function prepareResponses($filesUri, $requestedProps, $nodes) { - $responses = []; + $results = []; foreach ($nodes as $node) { $propFind = new PropFind($filesUri . $node->getPath(), $requestedProps); @@ -346,18 +343,9 @@ public function prepareResponses($filesUri, $requestedProps, $nodes) { $result = $propFind->getResultForMultiStatus(); $result['href'] = $propFind->getPath(); - $resourceType = $this->server->getResourceTypeForNode($node); - if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { - $result['href'] .= '/'; - } - - $responses[] = new Response( - rtrim($this->server->getBaseUri(), '/') . $filesUri . $node->getPath(), - $result, - 200 - ); + $results[] = $result; } - return $responses; + return $results; } /** @@ -378,10 +366,9 @@ public function findNodesByFileIds($rootNode, $fileIds) { $entry = $folder->getById($fileId); if ($entry) { $entry = current($entry); - if ($entry instanceof \OCP\Files\File) { - $results[] = new File($this->fileView, $entry); - } else if ($entry instanceof \OCP\Files\Folder) { - $results[] = new Directory($this->fileView, $entry); + $node = $this->makeSabreNode($entry); + if ($node) { + $results[] = $node; } } } @@ -389,6 +376,15 @@ public function findNodesByFileIds($rootNode, $fileIds) { return $results; } + private function makeSabreNode(\OCP\Files\Node $filesNode) { + if ($filesNode instanceof \OCP\Files\File) { + return new File($this->fileView, $filesNode); + } else if ($filesNode instanceof \OCP\Files\Folder) { + return new Directory($this->fileView, $filesNode); + } + throw new \Exception('Unrecognized Files API node returned, aborting'); + } + /** * Returns whether the currently logged in user is an administrator */ diff --git a/apps/dav/lib/Files/Xml/FilterRequest.php b/apps/dav/lib/Files/Xml/FilterRequest.php new file mode 100644 index 000000000000..8cbc6f73ebbd --- /dev/null +++ b/apps/dav/lib/Files/Xml/FilterRequest.php @@ -0,0 +1,110 @@ +next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + $elems = (array)$reader->parseInnerTree([ + '{DAV:}prop' => KeyValue::class, + '{http://owncloud.org/ns}filter-rules' => Base::class, + '{http://owncloud.org/ns}search' => KeyValue::class, + ]); + + $newProps = [ + 'filters' => [ + 'systemtag' => [], + 'favorite' => null + ], + 'properties' => [], + 'search' => null, + ]; + + if (!is_array($elems)) { + $elems = []; + } + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}prop' : + $newProps['properties'] = array_keys($elem['value']); + break; + case '{http://owncloud.org/ns}filter-rules' : + + foreach ($elem['value'] as $tag) { + if ($tag['name'] === '{http://owncloud.org/ns}systemtag') { + $newProps['filters']['systemtag'][] = $tag['value']; + } + if ($tag['name'] === '{http://owncloud.org/ns}favorite') { + $newProps['filters']['favorite'] = true; + } + } + break; + case '{http://owncloud.org/ns}search' : + $value = $elem['value']; + if (isset($value['{http://owncloud.org/ns}pattern'])) { + $newProps['search']['pattern'] = $value['{http://owncloud.org/ns}pattern']; + } + if (isset($value['{http://owncloud.org/ns}limit'])) { + $newProps['search']['limit'] = (int)$value['{http://owncloud.org/ns}limit']; + } + if (isset($value['{http://owncloud.org/ns}offset'])) { + $newProps['search']['offset'] = (int)$value['{http://owncloud.org/ns}offset']; + } + break; + } + } + + $obj = new self(); + foreach ($newProps as $key => $value) { + $obj->$key = $value; + } + + return $obj; + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php index e9ee0d94546d..65036329bd85 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -25,6 +25,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\FilesReportPlugin as FilesReportPluginImplementation; +use OCA\DAV\Files\Xml\FilterRequest; use OCP\SystemTag\ISystemTagObjectMapper; use OC\Files\View; use OCP\Files\Folder; @@ -32,6 +33,8 @@ use OCP\SystemTag\ISystemTagManager; use OCP\ITags; use OCP\Files\FileInfo; +use OCP\IRequest; +use OCP\IConfig; class FilesReportPluginTest extends \Test\TestCase { /** @var \Sabre\DAV\Server|\PHPUnit_Framework_MockObject_MockObject */ @@ -70,13 +73,11 @@ public function setUp() { ->disableOriginalConstructor() ->getMock(); - $this->view = $this->getMockBuilder('\OC\Files\View') - ->disableOriginalConstructor() - ->getMock(); + $this->view = new View(); $this->server = $this->getMockBuilder('\Sabre\DAV\Server') ->setConstructorArgs([$this->tree]) - ->setMethods(['getRequestUri', 'getBaseUri']) + ->setMethods(['getRequestUri', 'getBaseUri', 'generateMultiStatus']) ->getMock(); $this->server->expects($this->any()) @@ -109,6 +110,15 @@ public function setUp() { ->method('getUser') ->will($this->returnValue($user)); + // add FilesPlugin to test more properties + $this->server->addPlugin( + new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->createMock(IConfig::class), + $this->createMock(IRequest::class) + ) + ); + $this->plugin = new FilesReportPluginImplementation( $this->tree, $this->view, @@ -156,21 +166,16 @@ public function testOnReportInvalidReportName() { public function testOnReport() { $path = 'test'; - $parameters = [ - [ - 'name' => '{DAV:}prop', - 'value' => [ - ['name' => '{DAV:}getcontentlength', 'value' => ''], - ['name' => '{http://owncloud.org/ns}size', 'value' => ''], - ], - ], - [ - 'name' => '{http://owncloud.org/ns}filter-rules', - 'value' => [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], - ], - ], + $parameters = new FilterRequest(); + $parameters->properties = [ + '{DAV:}getcontentlength', + '{http://owncloud.org/ns}size', + '{http://owncloud.org/ns}fileid', + '{DAV:}resourcetype', + ]; + $parameters->filters = [ + 'systemtag' => [123, 456], + 'favorite' => null ]; $this->groupManager->expects($this->any()) @@ -186,14 +191,8 @@ public function testOnReport() { ->with('456', 'files') ->will($this->returnValue(['111', '222', '333'])); - $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') - ->disableOriginalConstructor() - ->getMock(); - - $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') - ->disableOriginalConstructor() - ->getMock(); - + $reportTargetNode = $this->createMock(\OCA\DAV\Connector\Sabre\Directory::class); + $response = $this->createMock(\Sabre\HTTP\ResponseInterface::class); $response->expects($this->once()) ->method('setHeader') ->with('Content-Type', 'application/xml; charset=utf-8'); @@ -210,12 +209,16 @@ public function testOnReport() { ->with('/' . $path) ->will($this->returnValue($reportTargetNode)); - $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder') - ->disableOriginalConstructor() - ->getMock(); - $filesNode2 = $this->getMockBuilder('\OCP\Files\File') - ->disableOriginalConstructor() - ->getMock(); + $filesNode1 = $this->createMock(\OCP\Files\Folder::class); + $filesNode1->method('getId')->willReturn(111); + $filesNode1->method('getPath')->willReturn('/node1'); + $filesNode1->method('isReadable')->willReturn(true); + $filesNode1->method('getSize')->willReturn(2048); + $filesNode2 = $this->createMock(\OCP\Files\File::class); + $filesNode2->method('getId')->willReturn(222); + $filesNode2->method('getPath')->willReturn('/sub/node2'); + $filesNode2->method('getSize')->willReturn(1024); + $filesNode2->method('isReadable')->willReturn(true); $this->userFolder->expects($this->at(0)) ->method('getById') @@ -232,7 +235,110 @@ public function testOnReport() { $this->server->httpResponse = $response; $this->plugin->initialize($this->server); + $responses = null; + $this->server->expects($this->once()) + ->method('generateMultiStatus') + ->will($this->returnCallback(function($responsesArg) use (&$responses) { + $responses = $responsesArg; + }) + ); + $this->assertFalse($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path)); + + $this->assertCount(2, $responses); + + $this->assertTrue(isset($responses[0][200])); + $this->assertTrue(isset($responses[1][200])); + + $this->assertEquals('/test/node1', $responses[0]['href']); + $this->assertEquals('/test/sub/node2', $responses[1]['href']); + + $props1 = $responses[0]; + $this->assertEquals('111', $props1[200]['{http://owncloud.org/ns}fileid']); + $this->assertNull($props1[404]['{DAV:}getcontentlength']); + $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props1[200]['{DAV:}resourcetype']); + $resourceType1 = $props1[200]['{DAV:}resourcetype']->getValue(); + $this->assertEquals('{DAV:}collection', $resourceType1[0]); + + $props2 = $responses[1]; + $this->assertEquals('1024', $props2[200]['{DAV:}getcontentlength']); + $this->assertEquals('222', $props2[200]['{http://owncloud.org/ns}fileid']); + $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props2[200]['{DAV:}resourcetype']); + $this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue()); + } + + public function testOnReportPaginationFiltered() { + $path = 'test'; + + $parameters = new FilterRequest(); + $parameters->properties = [ + '{DAV:}getcontentlength', + ]; + $parameters->filters = [ + 'systemtag' => [], + 'favorite' => true + ]; + $parameters->search = [ + 'offset' => 2, + 'limit' => 3, + ]; + + $filesNodes = []; + for ($i = 0; $i < 20; $i++) { + $filesNode = $this->createMock(\OCP\Files\File::class); + $filesNode->method('getId')->willReturn(1000 + $i); + $filesNode->method('getPath')->willReturn('/nodes/node' . $i); + $filesNode->method('isReadable')->willReturn(true); + $filesNodes[$filesNode->getId()] = $filesNode; + } + + // return all above nodes as favorites + $this->privateTags->expects($this->once()) + ->method('getFavorites') + ->will($this->returnValue(array_keys($filesNodes))); + + $reportTargetNode = $this->createMock(\OCA\DAV\Connector\Sabre\Directory::class); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($reportTargetNode)); + + // getById must only be called for the required nodes + $this->userFolder->expects($this->at(0)) + ->method('getById') + ->with(1002) + ->willReturn([$filesNodes[1002]]); + $this->userFolder->expects($this->at(1)) + ->method('getById') + ->with(1003) + ->willReturn([$filesNodes[1003]]); + $this->userFolder->expects($this->at(2)) + ->method('getById') + ->with(1004) + ->willReturn([$filesNodes[1004]]); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + + $this->plugin->initialize($this->server); + + $responses = null; + $this->server->expects($this->once()) + ->method('generateMultiStatus') + ->will($this->returnCallback(function($responsesArg) use (&$responses) { + $responses = $responsesArg; + }) + ); + + $this->assertFalse($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path)); + + $this->assertCount(3, $responses); + + $this->assertEquals('/test/nodes/node2', $responses[0]['href']); + $this->assertEquals('/test/nodes/node3', $responses[1]['href']); + $this->assertEquals('/test/nodes/node4', $responses[2]['href']); } public function testFindNodesByFileIdsRoot() { @@ -327,71 +433,6 @@ public function testFindNodesByFileIdsSubDir() { $this->assertEquals('second node', $result[1]->getName()); } - public function testPrepareResponses() { - $requestedProps = ['{DAV:}getcontentlength', '{http://owncloud.org/ns}fileid', '{DAV:}resourcetype']; - - $fileInfo = $this->createMock(FileInfo::class); - $fileInfo->method('isReadable')->willReturn(true); - - $node1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') - ->disableOriginalConstructor() - ->getMock(); - $node2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') - ->disableOriginalConstructor() - ->getMock(); - - $node1->expects($this->once()) - ->method('getInternalFileId') - ->will($this->returnValue('111')); - $node1->expects($this->any()) - ->method('getPath') - ->will($this->returnValue('/node1')); - $node1->method('getFileInfo')->willReturn($fileInfo); - $node2->expects($this->once()) - ->method('getInternalFileId') - ->will($this->returnValue('222')); - $node2->expects($this->once()) - ->method('getSize') - ->will($this->returnValue(1024)); - $node2->expects($this->any()) - ->method('getPath') - ->will($this->returnValue('/sub/node2')); - $node2->method('getFileInfo')->willReturn($fileInfo); - - $config = $this->createMock('\OCP\IConfig'); - - $this->server->addPlugin( - new \OCA\DAV\Connector\Sabre\FilesPlugin( - $this->tree, - $config, - $this->createMock('\OCP\IRequest') - ) - ); - $this->plugin->initialize($this->server); - $responses = $this->plugin->prepareResponses('/files/username', $requestedProps, [$node1, $node2]); - - $this->assertCount(2, $responses); - - $this->assertEquals(200, $responses[0]->getHttpStatus()); - $this->assertEquals(200, $responses[1]->getHttpStatus()); - - $this->assertEquals('http://example.com/owncloud/remote.php/dav/files/username/node1', $responses[0]->getHref()); - $this->assertEquals('http://example.com/owncloud/remote.php/dav/files/username/sub/node2', $responses[1]->getHref()); - - $props1 = $responses[0]->getResponseProperties(); - $this->assertEquals('111', $props1[200]['{http://owncloud.org/ns}fileid']); - $this->assertNull($props1[404]['{DAV:}getcontentlength']); - $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props1[200]['{DAV:}resourcetype']); - $resourceType1 = $props1[200]['{DAV:}resourcetype']->getValue(); - $this->assertEquals('{DAV:}collection', $resourceType1[0]); - - $props2 = $responses[1]->getResponseProperties(); - $this->assertEquals('1024', $props2[200]['{DAV:}getcontentlength']); - $this->assertEquals('222', $props2[200]['{http://owncloud.org/ns}fileid']); - $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props2[200]['{DAV:}resourcetype']); - $this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue()); - } - public function testProcessFilterRulesSingle() { $this->groupManager->expects($this->any()) ->method('isAdmin') @@ -407,7 +448,8 @@ public function testProcessFilterRulesSingle() { ]); $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + 'systemtag' => ['123'], + 'favorite' => null ]; $this->assertEquals(['111', '222'], $this->invokePrivate($this->plugin, 'processFilterRules', [$rules])); @@ -429,9 +471,10 @@ public function testProcessFilterRulesAndCondition() { ['456', 'files', 0, '', ['222', '333']], ]); + $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + 'systemtag' => ['123', '456'], + 'favorite' => null ]; $this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); @@ -454,8 +497,8 @@ public function testProcessFilterRulesAndConditionWithOneEmptyResult() { ]); $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + 'systemtag' => ['123', '456'], + 'favorite' => null ]; $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); @@ -478,8 +521,8 @@ public function testProcessFilterRulesAndConditionWithFirstEmptyResult() { ]); $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + 'systemtag' => ['123', '456'], + 'favorite' => null ]; $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); @@ -504,9 +547,8 @@ public function testProcessFilterRulesAndConditionWithEmptyMidResult() { ]); $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '789'], + 'systemtag' => ['123', '456', '789'], + 'favorite' => null ]; $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); @@ -547,8 +589,8 @@ public function testProcessFilterRulesInvisibleTagAsAdmin() { ->will($this->returnValue(['222', '333'])); $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + 'systemtag' => ['123', '456'], + 'favorite' => null ]; $this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); @@ -584,8 +626,8 @@ public function testProcessFilterRulesInvisibleTagAsUser() { ->will($this->returnValue([$tag1, $tag2])); $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + 'systemtag' => ['123', '456'], + 'favorite' => null ]; $this->invokePrivate($this->plugin, 'processFilterRules', [$rules]); @@ -627,8 +669,8 @@ public function testProcessFilterRulesVisibleTagAsUser() { ->will($this->returnValue(['222', '333'])); $rules = [ - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], - ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + 'systemtag' => ['123', '456'], + 'favorite' => null ]; $this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); @@ -636,7 +678,8 @@ public function testProcessFilterRulesVisibleTagAsUser() { public function testProcessFavoriteFilter() { $rules = [ - ['name' => '{http://owncloud.org/ns}favorite', 'value' => '1'], + 'systemtag' => [], + 'favorite' => true ]; $this->privateTags->expects($this->once()) diff --git a/tests/integration/features/bootstrap/WebDav.php b/tests/integration/features/bootstrap/WebDav.php index b4c4e63a5184..db4bc3c75c42 100644 --- a/tests/integration/features/bootstrap/WebDav.php +++ b/tests/integration/features/bootstrap/WebDav.php @@ -392,18 +392,33 @@ public function listFolder($user, $path, $folderDepth, $properties = null){ * @param string $properties properties which needs to be included in the report * @param string $filterRules filter-rules to choose what needs to appear in the report */ - public function reportFolder($user, $path, $properties, $filterRules){ + public function reportFolder($user, $path, $properties, $filterRules, $offset = null, $limit = null){ $client = $this->getSabreClient($user); $body = ' - - - ' . $properties . ' - - - ' . $filterRules . ' - - '; + + + ' . $properties . ' + + + ' . $filterRules . ' + '; + if (is_int($offset) || is_int($limit)) { + $body .= ' + '; + if (is_int($offset)) { + $body .= " + ${offset}"; + } + if (is_int($limit)) { + $body .= " + ${limit}"; + } + $body .= ' + '; + } + $body .= ' + '; $response = $client->request('REPORT', $this->makeSabrePath($user, $path), $body); $parsedResponse = $client->parseMultistatus($response['body']); @@ -757,6 +772,18 @@ public function thereAreNoDuplicateHeaders() { * @param \Behat\Gherkin\Node\TableNode|null $expectedElements */ public function checkFavoritedElements($user, $folder, $expectedElements){ + $this->checkFavoritedElementsPaginated($user, $folder, $expectedElements, null, null); + } + + /** + * @Then /^user "([^"]*)" in folder "([^"]*)" should have favorited the following elements from offset ([\d*]) and limit ([\d*])$/ + * @param string $user + * @param string $folder + * @param \Behat\Gherkin\Node\TableNode|null $expectedElements + * @param int $offset + * @param int $limit + */ + public function checkFavoritedElementsPaginated($user, $folder, $expectedElements, $offset, $limit){ $elementList = $this->reportFolder($user, $folder, '', diff --git a/tests/integration/features/favorites.feature b/tests/integration/features/favorites.feature index c2fdb1dfc118..9c15616fc3f9 100644 --- a/tests/integration/features/favorites.feature +++ b/tests/integration/features/favorites.feature @@ -147,3 +147,24 @@ Feature: favorite Then user "user1" in folder "/" should have favorited the following elements | /taken_out.txt | + Scenario: Get favorited elements paginated + Given using old dav path + And As an "admin" + And user "user0" exists + And user "user0" created a folder "/subfolder" + And User "user0" copies file "/textfile0.txt" to "/subfolder/textfile0.txt" + And User "user0" copies file "/textfile0.txt" to "/subfolder/textfile1.txt" + And User "user0" copies file "/textfile0.txt" to "/subfolder/textfile2.txt" + And User "user0" copies file "/textfile0.txt" to "/subfolder/textfile3.txt" + And User "user0" copies file "/textfile0.txt" to "/subfolder/textfile4.txt" + And User "user0" copies file "/textfile0.txt" to "/subfolder/textfile5.txt" + When user "user0" favorites element "/subfolder/textfile0.txt" + And user "user0" favorites element "/subfolder/textfile1.txt" + And user "user0" favorites element "/subfolder/textfile2.txt" + And user "user0" favorites element "/subfolder/textfile3.txt" + And user "user0" favorites element "/subfolder/textfile4.txt" + And user "user0" favorites element "/subfolder/textfile5.txt" + Then user "user0" in folder "/subfolder" should have favorited the following elements from offset 3 and limit 2 + | /subfolder/textfile2.txt | + | /subfolder/textfile3.txt | +