From ed546bd2a591d0d9029e9f3989e159f8b1e4e8c5 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 11 Jan 2016 18:09:00 +0100 Subject: [PATCH] Comments DAV implementation --- apps/dav/lib/comments/commentnode.php | 211 ++++++ apps/dav/lib/comments/commentsplugin.php | 243 +++++++ apps/dav/lib/comments/entitycollection.php | 148 ++++ .../dav/lib/comments/entitytypecollection.php | 120 ++++ apps/dav/lib/comments/rootcollection.php | 204 ++++++ apps/dav/lib/rootcollection.php | 8 + apps/dav/lib/server.php | 6 + apps/dav/tests/unit/comments/commentnode.php | 215 ++++++ .../tests/unit/comments/commentsplugin.php | 631 ++++++++++++++++++ .../tests/unit/comments/entitycollection.php | 113 ++++ .../unit/comments/entitytypecollection.php | 94 +++ .../tests/unit/comments/rootcollection.php | 160 +++++ lib/private/comments/comment.php | 4 +- lib/private/comments/manager.php | 4 +- 14 files changed, 2158 insertions(+), 3 deletions(-) create mode 100644 apps/dav/lib/comments/commentnode.php create mode 100644 apps/dav/lib/comments/commentsplugin.php create mode 100644 apps/dav/lib/comments/entitycollection.php create mode 100644 apps/dav/lib/comments/entitytypecollection.php create mode 100644 apps/dav/lib/comments/rootcollection.php create mode 100644 apps/dav/tests/unit/comments/commentnode.php create mode 100644 apps/dav/tests/unit/comments/commentsplugin.php create mode 100644 apps/dav/tests/unit/comments/entitycollection.php create mode 100644 apps/dav/tests/unit/comments/entitytypecollection.php create mode 100644 apps/dav/tests/unit/comments/rootcollection.php diff --git a/apps/dav/lib/comments/commentnode.php b/apps/dav/lib/comments/commentnode.php new file mode 100644 index 0000000000..b78b4765ca --- /dev/null +++ b/apps/dav/lib/comments/commentnode.php @@ -0,0 +1,211 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Comments; + + +use OCP\Comments\IComment; +use OCP\Comments\ICommentsManager; +use OCP\ILogger; +use OCP\IUserManager; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\PropPatch; + +class CommentNode implements \Sabre\DAV\INode, \Sabre\DAV\IProperties { + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + /** @var IComment */ + public $comment; + + /** @var ICommentsManager */ + protected $commentsManager; + + /** @var ILogger */ + protected $logger; + + /** @var array list of properties with key being their name and value their setter */ + protected $properties = []; + + /** @var IUserManager */ + protected $userManager; + + /** + * CommentNode constructor. + * + * @param ICommentsManager $commentsManager + * @param IComment $comment + * @param IUserManager $userManager + * @param ILogger $logger + */ + public function __construct( + ICommentsManager $commentsManager, + IComment $comment, + IUserManager $userManager, + ILogger $logger + ) { + $this->commentsManager = $commentsManager; + $this->comment = $comment; + $this->logger = $logger; + + $methods = get_class_methods($this->comment); + $methods = array_filter($methods, function($name){ + return strpos($name, 'get') === 0; + }); + foreach($methods as $getter) { + $name = '{'.self::NS_OWNCLOUD.'}' . lcfirst(substr($getter, 3)); + $this->properties[$name] = $getter; + } + $this->userManager = $userManager; + } + + /** + * returns a list of all possible property names + * + * @return array + */ + static public function getPropertyNames() { + return [ + '{http://owncloud.org/ns}id', + '{http://owncloud.org/ns}parentId', + '{http://owncloud.org/ns}topmostParentId', + '{http://owncloud.org/ns}childrenCount', + '{http://owncloud.org/ns}message', + '{http://owncloud.org/ns}verb', + '{http://owncloud.org/ns}actorType', + '{http://owncloud.org/ns}actorId', + '{http://owncloud.org/ns}actorDisplayName', + '{http://owncloud.org/ns}creationDateTime', + '{http://owncloud.org/ns}latestChildDateTime', + '{http://owncloud.org/ns}objectType', + '{http://owncloud.org/ns}objectId', + ]; + } + + /** + * Deleted the current node + * + * @return void + */ + function delete() { + $this->commentsManager->delete($this->comment->getId()); + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + return $this->comment->getId(); + } + + /** + * Renames the node + * + * @param string $name The new name + * @throws MethodNotAllowed + */ + function setName($name) { + throw new MethodNotAllowed(); + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + // we do not have a separate "mDateTime" field for updates currently. + return $this->comment->getCreationDateTime()->getTimestamp(); + } + + /** + * update the comment's message + * + * @param $propertyValue + * @return bool + */ + public function updateComment($propertyValue) { + try { + $this->comment->setMessage($propertyValue); + $this->commentsManager->save($this->comment); + return true; + } catch (\Exception $e) { + $this->logger->logException($e, ['app' => 'dav/comments']); + return false; + } + } + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $propPatch + * @return void + */ + function propPatch(PropPatch $propPatch) { + // other properties than 'message' are read only + $propPatch->handle('{'.self::NS_OWNCLOUD.'}message', [$this, 'updateComment']); + $propPatch->commit(); + } + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname + * + * If the array is empty, it means 'all properties' were requested. + * + * Note that it's fine to liberally give properties back, instead of + * conforming to the list of requested properties. + * The Server class will filter out the extra. + * + * @param array $properties + * @return array + */ + function getProperties($properties) { + $properties = array_keys($this->properties); + + $result = []; + foreach($properties as $property) { + $getter = $this->properties[$property]; + if(method_exists($this->comment, $getter)) { + $result[$property] = $this->comment->$getter(); + } + } + + if($this->comment->getActorType() === 'users') { + $user = $this->userManager->get($this->comment->getActorId()); + $displayName = is_null($user) ? null : $user->getDisplayName(); + $result['{' . self::NS_OWNCLOUD . '}actorDisplayName'] = $displayName; + } + + return $result; + } +} diff --git a/apps/dav/lib/comments/commentsplugin.php b/apps/dav/lib/comments/commentsplugin.php new file mode 100644 index 0000000000..59ce3f12f6 --- /dev/null +++ b/apps/dav/lib/comments/commentsplugin.php @@ -0,0 +1,243 @@ + + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Comments; + +use OCP\Comments\IComment; +use OCP\Comments\ICommentsManager; +use OCP\IUserSession; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\ReportNotSupported; +use Sabre\DAV\Exception\UnsupportedMediaType; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Xml\Element\Response; +use Sabre\DAV\Xml\Response\MultiStatus; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\Xml\Writer; + +/** + * Sabre plugin to handle comments: + */ +class CommentsPlugin extends ServerPlugin { + // namespace + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + const REPORT_PARAM_LIMIT = '{http://owncloud.org/ns}limit'; + const REPORT_PARAM_OFFSET = '{http://owncloud.org/ns}offset'; + const REPORT_PARAM_TIMESTAMP = '{http://owncloud.org/ns}datetime'; + + /** @var ICommentsManager */ + protected $commentsManager; + + /** @var \Sabre\DAV\Server $server */ + private $server; + + /** @var \OCP\IUserSession */ + protected $userSession; + + /** + * Comments plugin + * + * @param ICommentsManager $commentsManager + * @param IUserSession $userSession + */ + public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) { + $this->commentsManager = $commentsManager; + $this->userSession = $userSession; + } + + /** + * This initializes the plugin. + * + * This function is called by Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + $this->server = $server; + if(strpos($this->server->getRequestUri(), 'comments/') !== 0) { + return; + } + + $this->server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; + + $this->server->xml->classMap['DateTime'] = function(Writer $writer, \DateTime $value) { + $writer->write($value->format('Y-m-d H:m:i')); + }; + + $this->server->on('report', [$this, 'onReport']); + $this->server->on('method:POST', [$this, 'httpPost']); + } + + /** + * POST operation on Comments collections + * + * @param RequestInterface $request request object + * @param ResponseInterface $response response object + * @return null|false + */ + public function httpPost(RequestInterface $request, ResponseInterface $response) { + $path = $request->getPath(); + + // Making sure the node exists + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (NotFound $e) { + return null; + } + + if ($node instanceof EntityCollection) { + $data = $request->getBodyAsString(); + + $comment = $this->createComment( + $node->getName(), + $node->getId(), + $data, + $request->getHeader('Content-Type') + ); + $url = $request->getUrl() . '/' . urlencode($comment->getId()); + + $response->setHeader('Content-Location', $url); + + // created + $response->setStatus(201); + return false; + } + } + + /** + * REPORT operations to look for comments + * + * @param string $reportName + * @param [] $report + * @param string $uri + * @return bool + * @throws NotFound + * @throws ReportNotSupported + */ + public function onReport($reportName, $report, $uri) { + $node = $this->server->tree->getNodeForPath($uri); + if(!$node instanceof EntityCollection) { + throw new ReportNotSupported(); + } + $args = ['limit' => 0, 'offset' => 0, 'datetime' => null]; + $acceptableParameters = [ + $this::REPORT_PARAM_LIMIT, + $this::REPORT_PARAM_OFFSET, + $this::REPORT_PARAM_TIMESTAMP + ]; + $ns = '{' . $this::NS_OWNCLOUD . '}'; + foreach($report as $parameter) { + if(!in_array($parameter['name'], $acceptableParameters) || empty($parameter['value'])) { + continue; + } + $args[str_replace($ns, '', $parameter['name'])] = $parameter['value']; + } + + if(!is_null($args['datetime'])) { + $args['datetime'] = new \DateTime($args['datetime']); + } + + $results = $node->findChildren($args['limit'], $args['offset'], $args['datetime']); + + $responses = []; + foreach($results as $node) { + $nodePath = $this->server->getRequestUri() . '/' . $node->comment->getId(); + $resultSet = $this->server->getPropertiesForPath($nodePath, CommentNode::getPropertyNames()); + if(isset($resultSet[0]) && isset($resultSet[0][200])) { + $responses[] = new Response( + $this->server->getBaseUri() . $nodePath, + [200 => $resultSet[0][200]], + 200 + ); + } + + } + + $xml = $this->server->xml->write( + '{DAV:}multistatus', + new MultiStatus($responses) + ); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setBody($xml); + + return false; + } + + /** + * Creates a new comment + * + * @param string $objectType e.g. "files" + * @param string $objectId e.g. the file id + * @param string $data JSON encoded string containing the properties of the tag to create + * @param string $contentType content type of the data + * @return IComment newly created comment + * + * @throws BadRequest if a field was missing + * @throws UnsupportedMediaType if the content type is not supported + */ + private function createComment($objectType, $objectId, $data, $contentType = 'application/json') { + if (explode(';', $contentType)[0] === 'application/json') { + $data = json_decode($data, true); + } else { + throw new UnsupportedMediaType(); + } + + $actorType = $data['actorType']; + $actorId = null; + if($actorType === 'users') { + $user = $this->userSession->getUser(); + if(!is_null($user)) { + $actorId = $user->getUID(); + } + } + if(is_null($actorId)) { + throw new BadRequest('Invalid actor "' . $actorType .'"'); + } + + try { + $comment = $this->commentsManager->create($actorType, $actorId, $objectType, $objectId); + $properties = [ + 'message' => 'setMessage', + 'verb' => 'setVerb', + ]; + foreach($properties as $property => $setter) { + $comment->$setter($data[$property]); + } + $this->commentsManager->save($comment); + return $comment; + } catch (\InvalidArgumentException $e) { + throw new BadRequest('Invalid input values', 0, $e); + } + } + + + +} diff --git a/apps/dav/lib/comments/entitycollection.php b/apps/dav/lib/comments/entitycollection.php new file mode 100644 index 0000000000..a93569f6e4 --- /dev/null +++ b/apps/dav/lib/comments/entitycollection.php @@ -0,0 +1,148 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Comments; + +use OCP\Comments\ICommentsManager; +use OCP\Files\Folder; +use OCP\ILogger; +use OCP\IUserManager; +use Sabre\DAV\Exception\NotFound; + +/** + * Class EntityCollection + * + * this represents a specific holder of comments, identified by an entity type + * (class member $name) and an entity id (class member $id). + * + * @package OCA\DAV\Comments + */ +class EntityCollection extends RootCollection { + /** @var Folder */ + protected $fileRoot; + + /** @var string */ + protected $id; + + /** @var ILogger */ + protected $logger; + + /** + * @param string $id + * @param string $name + * @param ICommentsManager $commentsManager + * @param Folder $fileRoot + * @param IUserManager $userManager + * @param ILogger $logger + */ + public function __construct( + $id, + $name, + ICommentsManager $commentsManager, + Folder $fileRoot, + IUserManager $userManager, + ILogger $logger + ) { + foreach(['id', 'name'] as $property) { + $$property = trim($$property); + if(empty($$property) || !is_string($$property)) { + throw new \InvalidArgumentException('"' . $property . '" parameter must be non-empty string'); + } + } + $this->id = $id; + $this->name = $name; + $this->commentsManager = $commentsManager; + $this->fileRoot = $fileRoot; + $this->logger = $logger; + $this->userManager = $userManager; + } + + /** + * returns the ID of this entity + * + * @return string + */ + public function getId() { + return $this->id; + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @return \Sabre\DAV\INode + * @throws NotFound + */ + function getChild($name) { + try { + $comment = $this->commentsManager->get($name); + return new CommentNode($this->commentsManager, $comment, $this->userManager, $this->logger); + } catch (\OCP\Comments\NotFoundException $e) { + throw new NotFound(); + } + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + return $this->findChildren(); + } + + /** + * Returns an array of comment nodes. Result can be influenced by offset, + * limit and date time parameters. + * + * @param int $limit + * @param int $offset + * @param \DateTime|null $datetime + * @return CommentNode[] + */ + function findChildren($limit = 0, $offset = 0, \DateTime $datetime = null) { + $comments = $this->commentsManager->getForObject($this->name, $this->id, $limit, $offset, $datetime); + $result = []; + foreach($comments as $comment) { + $result[] = new CommentNode($this->commentsManager, $comment, $this->userManager, $this->logger); + } + return $result; + } + + /** + * Checks if a child-node with the specified name exists + * + * @param string $name + * @return bool + */ + function childExists($name) { + try { + $this->commentsManager->get($name); + return true; + } catch (\OCP\Comments\NotFoundException $e) { + return false; + } + } +} + diff --git a/apps/dav/lib/comments/entitytypecollection.php b/apps/dav/lib/comments/entitytypecollection.php new file mode 100644 index 0000000000..544838b89e --- /dev/null +++ b/apps/dav/lib/comments/entitytypecollection.php @@ -0,0 +1,120 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Comments; + +use OCP\Comments\ICommentsManager; +use OCP\Files\Folder; +use OCP\ILogger; +use OCP\IUserManager; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\MethodNotAllowed; + +/** + * Class EntityTypeCollection + * + * This is collection on the type of things a user can leave comments on, for + * example: 'files'. + * + * Its children are instances of EntityCollection (representing a specific + * object, for example the file by id). + * + * @package OCA\DAV\Comments + */ +class EntityTypeCollection extends RootCollection { + /** @var Folder */ + protected $fileRoot; + + /** @var ILogger */ + protected $logger; + + /** + * @param string $name + * @param ICommentsManager $commentsManager + * @param Folder $fileRoot + * @param IUserManager $userManager + * @param ILogger $logger + */ + public function __construct( + $name, + ICommentsManager $commentsManager, + Folder $fileRoot, + IUserManager $userManager, + ILogger $logger + ) { + $name = trim($name); + if(empty($name) || !is_string($name)) { + throw new \InvalidArgumentException('"name" parameter must be non-empty string'); + } + $this->name = $name; + $this->commentsManager = $commentsManager; + $this->fileRoot = $fileRoot; + $this->logger = $logger; + $this->userManager = $userManager; + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @return \Sabre\DAV\INode + * @throws Forbidden + */ + function getChild($name) { + if(!$this->childExists($name)) { + throw new Forbidden('Entity does not exist or is not available'); + } + return new EntityCollection( + $name, + $this->name, + $this->commentsManager, + $this->fileRoot, + $this->userManager, + $this->logger + ); + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + * @throws MethodNotAllowed + */ + function getChildren() { + throw new MethodNotAllowed('No permission to list folder contents'); + } + + /** + * Checks if a child-node with the specified name exists + * + * @param string $name + * @return bool + */ + function childExists($name) { + $nodes = $this->fileRoot->getById($name); + return !empty($nodes); + } + + +} diff --git a/apps/dav/lib/comments/rootcollection.php b/apps/dav/lib/comments/rootcollection.php new file mode 100644 index 0000000000..aec8e65566 --- /dev/null +++ b/apps/dav/lib/comments/rootcollection.php @@ -0,0 +1,204 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Comments; + +use OCP\Comments\ICommentsManager; +use OCP\Files\IRootFolder; +use OCP\ILogger; +use OCP\IUserManager; +use OCP\IUserSession; +use Sabre\DAV\Exception\NotAuthenticated; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; + +class RootCollection implements ICollection { + + /** @var EntityTypeCollection[] */ + private $entityTypeCollections = []; + + /** @var ICommentsManager */ + protected $commentsManager; + + /** @var string */ + protected $name = 'comments'; + + /** @var ILogger */ + protected $logger; + + /** @var IUserManager */ + protected $userManager; + /** + * @var IUserSession + */ + protected $userSession; + /** + * @var IRootFolder + */ + protected $rootFolder; + + /** + * @param ICommentsManager $commentsManager + * @param IUserManager $userManager + * @param IUserSession $userSession + * @param IRootFolder $rootFolder + * @param ILogger $logger + */ + public function __construct( + ICommentsManager $commentsManager, + IUserManager $userManager, + IUserSession $userSession, + IRootFolder $rootFolder, + ILogger $logger) + { + $this->commentsManager = $commentsManager; + $this->logger = $logger; + $this->userManager = $userManager; + $this->userSession = $userSession; + $this->rootFolder = $rootFolder; + } + + /** + * initializes the collection. At this point of time, we need the logged in + * user. Since it is not the case when the instance is created, we cannot + * have this in the constructor. + * + * @throws NotAuthenticated + */ + protected function initCollections() { + if(!empty($this->entityTypeCollections)) { + return; + } + $user = $this->userSession->getUser(); + if(is_null($user)) { + throw new NotAuthenticated(); + } + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $this->entityTypeCollections['files'] = new EntityTypeCollection( + 'files', + $this->commentsManager, + $userFolder, + $this->userManager, + $this->logger + ); + } + + /** + * Creates a new file in the directory + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @return null|string + * @throws Forbidden + */ + function createFile($name, $data = null) { + throw new Forbidden('Cannot create comments by id'); + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @throws Forbidden + */ + function createDirectory($name) { + throw new Forbidden('Permission denied to create collections'); + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @return \Sabre\DAV\INode + * @throws NotFound + */ + function getChild($name) { + $this->initCollections(); + if(isset($this->entityTypeCollections[$name])) { + return $this->entityTypeCollections[$name]; + } + throw new NotFound('Entity type "' . $name . '" not found."'); + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + $this->initCollections(); + return $this->entityTypeCollections; + } + + /** + * Checks if a child-node with the specified name exists + * + * @param string $name + * @return bool + */ + function childExists($name) { + $this->initCollections(); + return isset($this->entityTypeCollections[$name]); + } + + /** + * Deleted the current node + * + * @throws Forbidden + */ + function delete() { + throw new Forbidden('Permission denied to delete this collection'); + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + return $this->name; + } + + /** + * Renames the node + * + * @param string $name The new name + * @throws Forbidden + */ + function setName($name) { + throw new Forbidden('Permission denied to rename this collection'); + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + return null; + } +} diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php index bfd1aefb05..b7c2995dba 100644 --- a/apps/dav/lib/rootcollection.php +++ b/apps/dav/lib/rootcollection.php @@ -70,6 +70,13 @@ class RootCollection extends SimpleCollection { \OC::$server->getUserSession(), \OC::$server->getGroupManager() ); + $commentsCollection = new Comments\RootCollection( + \OC::$server->getCommentsManager(), + \OC::$server->getUserManager(), + \OC::$server->getUserSession(), + \OC::$server->getRootFolder(), + \OC::$server->getLogger() + ); $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend); $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, 'principals/users'); @@ -91,6 +98,7 @@ class RootCollection extends SimpleCollection { $systemAddressBookRoot]), $systemTagCollection, $systemTagRelationsCollection, + $commentsCollection, ]; parent::__construct('root', $children); diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index 78867ad2f4..a0ba9c1aa9 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -90,6 +90,12 @@ class Server { // system tags plugins $this->server->addPlugin(new \OCA\DAV\SystemTag\SystemTagPlugin(\OC::$server->getSystemTagManager())); + // comments plugin + $this->server->addPlugin(new \OCA\DAV\Comments\CommentsPlugin( + \OC::$server->getCommentsManager(), + \OC::$server->getUserSession() + )); + // Finder on OS X requires Class 2 WebDAV support (locking), since we do // not provide locking we emulate it using a fake locking plugin. if($request->isUserAgent(['/WebDAVFS/'])) { diff --git a/apps/dav/tests/unit/comments/commentnode.php b/apps/dav/tests/unit/comments/commentnode.php new file mode 100644 index 0000000000..b224bee909 --- /dev/null +++ b/apps/dav/tests/unit/comments/commentnode.php @@ -0,0 +1,215 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Tests\Unit\Comments; + +use OCA\DAV\Comments\CommentNode; + +class CommentsNode extends \Test\TestCase { + + protected $commentsManager; + protected $comment; + protected $node; + protected $userManager; + protected $logger; + + public function setUp() { + parent::setUp(); + + $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); + $this->comment = $this->getMock('\OCP\Comments\IComment'); + $this->userManager = $this->getMock('\OCP\IUserManager'); + $this->logger = $this->getMock('\OCP\ILogger'); + + $this->node = new CommentNode($this->commentsManager, $this->comment, $this->userManager, $this->logger); + } + + public function testDelete() { + $this->comment->expects($this->once()) + ->method('getId') + ->will($this->returnValue('19')); + + $this->commentsManager->expects($this->once()) + ->method('delete') + ->with('19'); + + $this->node->delete(); + } + + public function testGetName() { + $id = '19'; + $this->comment->expects($this->once()) + ->method('getId') + ->will($this->returnValue($id)); + + $this->assertSame($this->node->getName(), $id); + } + + /** + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + */ + public function testSetName() { + $this->node->setName('666'); + } + + public function testGetLastModified() { + $dateTime = new \DateTime('2016-01-10 18:48:00'); + $this->comment->expects($this->once()) + ->method('getCreationDateTime') + ->will($this->returnValue($dateTime)); + + $this->assertSame($this->node->getLastModified(), $dateTime->getTimestamp()); + } + + public function testUpdateComment() { + $msg = 'Hello Earth'; + + $this->comment->expects($this->once()) + ->method('setMessage') + ->with($msg); + + $this->commentsManager->expects($this->once()) + ->method('save') + ->with($this->comment); + + $this->assertTrue($this->node->updateComment($msg)); + } + + public function testUpdateCommentException() { + $msg = null; + + $this->comment->expects($this->once()) + ->method('setMessage') + ->with($msg) + ->will($this->throwException(new \Exception('buh!'))); + + $this->commentsManager->expects($this->never()) + ->method('save'); + + $this->logger->expects($this->once()) + ->method('logException'); + + $this->assertFalse($this->node->updateComment($msg)); + } + + public function testPropPatch() { + $propPatch = $this->getMockBuilder('Sabre\DAV\PropPatch') + ->disableOriginalConstructor() + ->getMock(); + + $propPatch->expects($this->once()) + ->method('handle') + ->with('{http://owncloud.org/ns}message'); + + $propPatch->expects($this->once()) + ->method('commit'); + + $this->node->propPatch($propPatch); + } + + public function testGetProperties() { + $ns = '{http://owncloud.org/ns}'; + $expected = [ + $ns . 'id' => '123', + $ns . 'parentId' => '12', + $ns . 'topmostParentId' => '2', + $ns . 'childrenCount' => 3, + $ns . 'message' => 'such a nice file you haveā€¦', + $ns . 'verb' => 'comment', + $ns . 'actorType' => 'users', + $ns . 'actorId' => 'alice', + $ns . 'actorDisplayName' => 'Alice of Wonderland', + $ns . 'creationDateTime' => new \DateTime('2016-01-10 18:48:00'), + $ns . 'latestChildDateTime' => new \DateTime('2016-01-12 18:48:00'), + $ns . 'objectType' => 'files', + $ns . 'objectId' => '1848', + ]; + + $this->comment->expects($this->once()) + ->method('getId') + ->will($this->returnValue($expected[$ns . 'id'])); + + $this->comment->expects($this->once()) + ->method('getParentId') + ->will($this->returnValue($expected[$ns . 'parentId'])); + + $this->comment->expects($this->once()) + ->method('getTopmostParentId') + ->will($this->returnValue($expected[$ns . 'topmostParentId'])); + + $this->comment->expects($this->once()) + ->method('getChildrenCount') + ->will($this->returnValue($expected[$ns . 'childrenCount'])); + + $this->comment->expects($this->once()) + ->method('getMessage') + ->will($this->returnValue($expected[$ns . 'message'])); + + $this->comment->expects($this->once()) + ->method('getVerb') + ->will($this->returnValue($expected[$ns . 'verb'])); + + $this->comment->expects($this->exactly(2)) + ->method('getActorType') + ->will($this->returnValue($expected[$ns . 'actorType'])); + + $this->comment->expects($this->exactly(2)) + ->method('getActorId') + ->will($this->returnValue($expected[$ns . 'actorId'])); + + $this->comment->expects($this->once()) + ->method('getCreationDateTime') + ->will($this->returnValue($expected[$ns . 'creationDateTime'])); + + $this->comment->expects($this->once()) + ->method('getLatestChildDateTime') + ->will($this->returnValue($expected[$ns . 'latestChildDateTime'])); + + $this->comment->expects($this->once()) + ->method('getObjectType') + ->will($this->returnValue($expected[$ns . 'objectType'])); + + $this->comment->expects($this->once()) + ->method('getObjectId') + ->will($this->returnValue($expected[$ns . 'objectId'])); + + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('getDisplayName') + ->will($this->returnValue($expected[$ns . 'actorDisplayName'])); + + $this->userManager->expects($this->once()) + ->method('get') + ->with('alice') + ->will($this->returnValue($user)); + + $properties = $this->node->getProperties(null); + + foreach($properties as $name => $value) { + $this->assertTrue(isset($expected[$name])); + $this->assertSame($expected[$name], $value); + unset($expected[$name]); + } + $this->assertTrue(empty($expected)); + } +} diff --git a/apps/dav/tests/unit/comments/commentsplugin.php b/apps/dav/tests/unit/comments/commentsplugin.php new file mode 100644 index 0000000000..5fcccab90b --- /dev/null +++ b/apps/dav/tests/unit/comments/commentsplugin.php @@ -0,0 +1,631 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Tests\Unit\Comments; + +use OC\Comments\Comment; +use OCA\DAV\Comments\CommentsPlugin as CommentsPluginImplementation; + +class CommentsPlugin extends \Test\TestCase { + /** @var \Sabre\DAV\Server */ + private $server; + + /** @var \Sabre\DAV\Tree */ + private $tree; + + /** @var \OCP\Comments\ICommentsManager */ + private $commentsManager; + + /** @var \OCP\IUserSession */ + private $userSession; + + /** @var CommentsPluginImplementation */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + + $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + ->setConstructorArgs([$this->tree]) + ->setMethods(['getRequestUri']) + ->getMock(); + + $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); + $this->userSession = $this->getMock('\OCP\IUserSession'); + + $this->plugin = new CommentsPluginImplementation($this->commentsManager, $this->userSession); + } + + public function testCreateComment() { + $commentData = [ + 'actorType' => 'users', + 'verb' => 'comment', + 'message' => 'my first comment', + ]; + + $comment = new Comment([ + 'objectType' => 'files', + 'objectId' => '42', + 'actorType' => 'users', + 'actorId' => 'alice' + ] + $commentData); + $comment->setId('23'); + + $path = 'comments/files/42'; + + $requestData = json_encode($commentData); + + $user = $this->getMock('OCP\IUser'); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('alice')); + + $node = $this->getMockBuilder('\OCA\DAV\Comments\EntityCollection') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->once()) + ->method('getName') + ->will($this->returnValue('files')); + $node->expects($this->once()) + ->method('getId') + ->will($this->returnValue('42')); + + $this->commentsManager->expects($this->once()) + ->method('create') + ->with('users', 'alice', 'files', '42') + ->will($this->returnValue($comment)); + + $this->userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + + // technically, this is a shortcut. Inbetween EntityTypeCollection would + // be returned, but doing it exactly right would not be really + // unit-testing like, as it would require to haul in a lot of other + // things. + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($node)); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/' . $path)); + + $request->expects($this->once()) + ->method('getBodyAsString') + ->will($this->returnValue($requestData)); + + $request->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue('application/json')); + + $request->expects($this->once()) + ->method('getUrl') + ->will($this->returnValue('http://example.com/dav/' . $path)); + + $response->expects($this->once()) + ->method('setHeader') + ->with('Content-Location', 'http://example.com/dav/' . $path . '/23'); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->httpPost($request, $response); + } + + public function testCreateCommentInvalidObject() { + $commentData = [ + 'actorType' => 'users', + 'verb' => 'comment', + 'message' => 'my first comment', + ]; + + $comment = new Comment([ + 'objectType' => 'files', + 'objectId' => '666', + 'actorType' => 'users', + 'actorId' => 'alice' + ] + $commentData); + $comment->setId('23'); + + $path = 'comments/files/666'; + + $user = $this->getMock('OCP\IUser'); + $user->expects($this->never()) + ->method('getUID'); + + $node = $this->getMockBuilder('\OCA\DAV\Comments\EntityCollection') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->never()) + ->method('getName'); + $node->expects($this->never()) + ->method('getId'); + + $this->commentsManager->expects($this->never()) + ->method('create'); + + $this->userSession->expects($this->never()) + ->method('getUser'); + + // technically, this is a shortcut. Inbetween EntityTypeCollection would + // be returned, but doing it exactly right would not be really + // unit-testing like, as it would require to haul in a lot of other + // things. + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->throwException(new \Sabre\DAV\Exception\NotFound())); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/' . $path)); + + $request->expects($this->never()) + ->method('getBodyAsString'); + + $request->expects($this->never()) + ->method('getHeader') + ->with('Content-Type'); + + $request->expects($this->never()) + ->method('getUrl'); + + $response->expects($this->never()) + ->method('setHeader'); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->httpPost($request, $response); + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + public function testCreateCommentInvalidActor() { + $commentData = [ + 'actorType' => 'robots', + 'verb' => 'comment', + 'message' => 'my first comment', + ]; + + $comment = new Comment([ + 'objectType' => 'files', + 'objectId' => '42', + 'actorType' => 'users', + 'actorId' => 'alice' + ] + $commentData); + $comment->setId('23'); + + $path = 'comments/files/42'; + + $requestData = json_encode($commentData); + + $user = $this->getMock('OCP\IUser'); + $user->expects($this->never()) + ->method('getUID'); + + $node = $this->getMockBuilder('\OCA\DAV\Comments\EntityCollection') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->once()) + ->method('getName') + ->will($this->returnValue('files')); + $node->expects($this->once()) + ->method('getId') + ->will($this->returnValue('42')); + + $this->commentsManager->expects($this->never()) + ->method('create'); + + $this->userSession->expects($this->never()) + ->method('getUser'); + + // technically, this is a shortcut. Inbetween EntityTypeCollection would + // be returned, but doing it exactly right would not be really + // unit-testing like, as it would require to haul in a lot of other + // things. + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($node)); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/' . $path)); + + $request->expects($this->once()) + ->method('getBodyAsString') + ->will($this->returnValue($requestData)); + + $request->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue('application/json')); + + $request->expects($this->never()) + ->method('getUrl'); + + $response->expects($this->never()) + ->method('setHeader'); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->httpPost($request, $response); + } + + /** + * @expectedException \Sabre\DAV\Exception\UnsupportedMediaType + */ + public function testCreateCommentUnsupportedMediaType() { + $commentData = [ + 'actorType' => 'users', + 'verb' => 'comment', + 'message' => 'my first comment', + ]; + + $comment = new Comment([ + 'objectType' => 'files', + 'objectId' => '42', + 'actorType' => 'users', + 'actorId' => 'alice' + ] + $commentData); + $comment->setId('23'); + + $path = 'comments/files/42'; + + $requestData = json_encode($commentData); + + $user = $this->getMock('OCP\IUser'); + $user->expects($this->never()) + ->method('getUID'); + + $node = $this->getMockBuilder('\OCA\DAV\Comments\EntityCollection') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->once()) + ->method('getName') + ->will($this->returnValue('files')); + $node->expects($this->once()) + ->method('getId') + ->will($this->returnValue('42')); + + $this->commentsManager->expects($this->never()) + ->method('create'); + + $this->userSession->expects($this->never()) + ->method('getUser'); + + // technically, this is a shortcut. Inbetween EntityTypeCollection would + // be returned, but doing it exactly right would not be really + // unit-testing like, as it would require to haul in a lot of other + // things. + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($node)); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/' . $path)); + + $request->expects($this->once()) + ->method('getBodyAsString') + ->will($this->returnValue($requestData)); + + $request->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue('application/trumpscript')); + + $request->expects($this->never()) + ->method('getUrl'); + + $response->expects($this->never()) + ->method('setHeader'); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->httpPost($request, $response); + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + public function testCreateCommentInvalidPayload() { + $commentData = [ + 'actorType' => 'users', + 'verb' => '', + 'message' => '', + ]; + + $comment = new Comment([ + 'objectType' => 'files', + 'objectId' => '42', + 'actorType' => 'users', + 'actorId' => 'alice', + 'message' => 'dummy', + 'verb' => 'dummy' + ]); + $comment->setId('23'); + + $path = 'comments/files/42'; + + $requestData = json_encode($commentData); + + $user = $this->getMock('OCP\IUser'); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('alice')); + + $node = $this->getMockBuilder('\OCA\DAV\Comments\EntityCollection') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->once()) + ->method('getName') + ->will($this->returnValue('files')); + $node->expects($this->once()) + ->method('getId') + ->will($this->returnValue('42')); + + $this->commentsManager->expects($this->once()) + ->method('create') + ->with('users', 'alice', 'files', '42') + ->will($this->returnValue($comment)); + + $this->commentsManager->expects($this->any()) + ->method('setMessage') + ->with('') + ->will($this->throwException(new \InvalidArgumentException())); + + $this->commentsManager->expects($this->any()) + ->method('setVerb') + ->with('') + ->will($this->throwException(new \InvalidArgumentException())); + + $this->userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + + // technically, this is a shortcut. Inbetween EntityTypeCollection would + // be returned, but doing it exactly right would not be really + // unit-testing like, as it would require to haul in a lot of other + // things. + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($node)); + + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/' . $path)); + + $request->expects($this->once()) + ->method('getBodyAsString') + ->will($this->returnValue($requestData)); + + $request->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue('application/json')); + + $request->expects($this->never()) + ->method('getUrl'); + + $response->expects($this->never()) + ->method('setHeader'); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->httpPost($request, $response); + } + + /** + * @expectedException \Sabre\DAV\Exception\ReportNotSupported + */ + public function testOnReportInvalidNode() { + $path = 'totally/unrelated/13'; + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($this->getMock('\Sabre\DAV\INode'))); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->onReport('', [], '/' . $path); + } + + public function testOnReportDateTimeEmpty() { + $path = 'comments/files/42'; + + $parameters = [ + [ + 'name' => '{http://owncloud.org/ns}limit', + 'value' => 5, + ], + [ + 'name' => '{http://owncloud.org/ns}offset', + 'value' => 10, + ], + [ + 'name' => '{http://owncloud.org/ns}datetime', + 'value' => '', + ] + ]; + + $node = $this->getMockBuilder('\OCA\DAV\Comments\EntityCollection') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->once()) + ->method('findChildren') + ->with(5, 10, null) + ->will($this->returnValue([])); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response->expects($this->once()) + ->method('setHeader') + ->with('Content-Type', 'application/xml; charset=utf-8'); + + $response->expects($this->once()) + ->method('setStatus') + ->with(207); + + $response->expects($this->once()) + ->method('setBody'); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($node)); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->server->httpResponse = $response; + $this->plugin->initialize($this->server); + + $this->plugin->onReport('', $parameters, '/' . $path); + } + + public function testOnReport() { + $path = 'comments/files/42'; + + $parameters = [ + [ + 'name' => '{http://owncloud.org/ns}limit', + 'value' => 5, + ], + [ + 'name' => '{http://owncloud.org/ns}offset', + 'value' => 10, + ], + [ + 'name' => '{http://owncloud.org/ns}datetime', + 'value' => '2016-01-10 18:48:00', + ] + ]; + + $node = $this->getMockBuilder('\OCA\DAV\Comments\EntityCollection') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->once()) + ->method('findChildren') + ->with(5, 10, new \DateTime($parameters[2]['value'])) + ->will($this->returnValue([])); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response->expects($this->once()) + ->method('setHeader') + ->with('Content-Type', 'application/xml; charset=utf-8'); + + $response->expects($this->once()) + ->method('setStatus') + ->with(207); + + $response->expects($this->once()) + ->method('setBody'); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($node)); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->server->httpResponse = $response; + $this->plugin->initialize($this->server); + + $this->plugin->onReport('', $parameters, '/' . $path); + } + + + +} diff --git a/apps/dav/tests/unit/comments/entitycollection.php b/apps/dav/tests/unit/comments/entitycollection.php new file mode 100644 index 0000000000..81442c7a87 --- /dev/null +++ b/apps/dav/tests/unit/comments/entitycollection.php @@ -0,0 +1,113 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Tests\Unit\Comments; + +class EntityCollection extends \Test\TestCase { + + protected $commentsManager; + protected $folder; + protected $userManager; + protected $logger; + protected $collection; + + public function setUp() { + parent::setUp(); + + $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); + $this->folder = $this->getMock('\OCP\Files\Folder'); + $this->userManager = $this->getMock('\OCP\IUserManager'); + $this->logger = $this->getMock('\OCP\ILogger'); + + $this->collection = new \OCA\DAV\Comments\EntityCollection( + '19', + 'files', + $this->commentsManager, + $this->folder, + $this->userManager, + $this->logger + ); + } + + public function testGetId() { + $this->assertSame($this->collection->getId(), '19'); + } + + public function testGetChild() { + $this->commentsManager->expects($this->once()) + ->method('get') + ->with('55') + ->will($this->returnValue($this->getMock('\OCP\Comments\IComment'))); + + $node = $this->collection->getChild('55'); + $this->assertTrue($node instanceof \OCA\DAV\Comments\CommentNode); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotFound + */ + public function testGetChildException() { + $this->commentsManager->expects($this->once()) + ->method('get') + ->with('55') + ->will($this->throwException(new \OCP\Comments\NotFoundException())); + + $this->collection->getChild('55'); + } + + public function testGetChildren() { + $this->commentsManager->expects($this->once()) + ->method('getForObject') + ->with('files', '19') + ->will($this->returnValue([$this->getMock('\OCP\Comments\IComment')])); + + $result = $this->collection->getChildren(); + + $this->assertSame(count($result), 1); + $this->assertTrue($result[0] instanceof \OCA\DAV\Comments\CommentNode); + } + + public function testFindChildren() { + $dt = new \DateTime('2016-01-10 18:48:00'); + $this->commentsManager->expects($this->once()) + ->method('getForObject') + ->with('files', '19', 5, 15, $dt) + ->will($this->returnValue([$this->getMock('\OCP\Comments\IComment')])); + + $result = $this->collection->findChildren(5, 15, $dt); + + $this->assertSame(count($result), 1); + $this->assertTrue($result[0] instanceof \OCA\DAV\Comments\CommentNode); + } + + public function testChildExistsTrue() { + $this->assertTrue($this->collection->childExists('44')); + } + + public function testChildExistsFalse() { + $this->commentsManager->expects($this->once()) + ->method('get') + ->with('44') + ->will($this->throwException(new \OCP\Comments\NotFoundException())); + + $this->assertFalse($this->collection->childExists('44')); + } +} diff --git a/apps/dav/tests/unit/comments/entitytypecollection.php b/apps/dav/tests/unit/comments/entitytypecollection.php new file mode 100644 index 0000000000..72e90a5713 --- /dev/null +++ b/apps/dav/tests/unit/comments/entitytypecollection.php @@ -0,0 +1,94 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Tests\Unit\Comments; + +use OCA\DAV\Comments\EntityCollection as EntityCollectionImplemantation; + +class EntityTypeCollection extends \Test\TestCase { + + protected $commentsManager; + protected $folder; + protected $userManager; + protected $logger; + protected $collection; + + public function setUp() { + parent::setUp(); + + $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); + $this->folder = $this->getMock('\OCP\Files\Folder'); + $this->userManager = $this->getMock('\OCP\IUserManager'); + $this->logger = $this->getMock('\OCP\ILogger'); + + $this->collection = new \OCA\DAV\Comments\EntityTypeCollection( + 'files', + $this->commentsManager, + $this->folder, + $this->userManager, + $this->logger + ); + } + + public function testChildExistsYes() { + $this->folder->expects($this->once()) + ->method('getById') + ->with('17') + ->will($this->returnValue([$this->getMock('\OCP\Files\Node')])); + $this->assertTrue($this->collection->childExists('17')); + } + + public function testChildExistsNo() { + $this->folder->expects($this->once()) + ->method('getById') + ->will($this->returnValue([])); + $this->assertFalse($this->collection->childExists('17')); + } + + public function testGetChild() { + $this->folder->expects($this->once()) + ->method('getById') + ->with('17') + ->will($this->returnValue([$this->getMock('\OCP\Files\Node')])); + + $ec = $this->collection->getChild('17'); + $this->assertTrue($ec instanceof EntityCollectionImplemantation); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testGetChildException() { + $this->folder->expects($this->once()) + ->method('getById') + ->with('17') + ->will($this->returnValue([])); + + $this->collection->getChild('17'); + } + + /** + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + */ + public function testGetChildren() { + $this->collection->getChildren(); + } +} diff --git a/apps/dav/tests/unit/comments/rootcollection.php b/apps/dav/tests/unit/comments/rootcollection.php new file mode 100644 index 0000000000..369006e715 --- /dev/null +++ b/apps/dav/tests/unit/comments/rootcollection.php @@ -0,0 +1,160 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Tests\Unit\Comments; + +use OCA\DAV\Comments\EntityTypeCollection as EntityTypeCollectionImplementation; + +class RootCollection extends \Test\TestCase { + + protected $commentsManager; + protected $userManager; + protected $logger; + protected $collection; + protected $userSession; + protected $rootFolder; + protected $user; + + public function setUp() { + parent::setUp(); + + $this->user = $this->getMock('\OCP\IUser'); + + $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); + $this->userManager = $this->getMock('\OCP\IUserManager'); + $this->userSession = $this->getMock('\OCP\IUserSession'); + $this->rootFolder = $this->getMock('\OCP\Files\IRootFolder'); + $this->logger = $this->getMock('\OCP\ILogger'); + + $this->collection = new \OCA\DAV\Comments\RootCollection( + $this->commentsManager, + $this->userManager, + $this->userSession, + $this->rootFolder, + $this->logger + ); + } + + protected function prepareForInitCollections() { + $this->user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('alice')); + + $this->userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($this->user)); + + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->with('alice') + ->will($this->returnValue($this->getMock('\OCP\Files\Folder'))); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testCreateFile() { + $this->collection->createFile('foo'); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testCreateDirectory() { + $this->collection->createDirectory('foo'); + } + + public function testGetChild() { + $this->prepareForInitCollections(); + $etc = $this->collection->getChild('files'); + $this->assertTrue($etc instanceof EntityTypeCollectionImplementation); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotFound + */ + public function testGetChildInvalid() { + $this->prepareForInitCollections(); + $this->collection->getChild('robots'); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotAuthenticated + */ + public function testGetChildNoAuth() { + $this->collection->getChild('files'); + } + + public function testGetChildren() { + $this->prepareForInitCollections(); + $children = $this->collection->getChildren(); + $this->assertFalse(empty($children)); + foreach($children as $child) { + $this->assertTrue($child instanceof EntityTypeCollectionImplementation); + } + } + + /** + * @expectedException \Sabre\DAV\Exception\NotAuthenticated + */ + public function testGetChildrenNoAuth() { + $this->collection->getChildren(); + } + + public function testChildExistsYes() { + $this->prepareForInitCollections(); + $this->assertTrue($this->collection->childExists('files')); + } + + public function testChildExistsNo() { + $this->prepareForInitCollections(); + $this->assertFalse($this->collection->childExists('robots')); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotAuthenticated + */ + public function testChildExistsNoAuth() { + $this->collection->childExists('files'); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDelete() { + $this->collection->delete(); + } + + public function testGetName() { + $this->assertSame('comments', $this->collection->getName()); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testSetName() { + $this->collection->setName('foobar'); + } + + public function testGetLastModified() { + $this->assertSame(null, $this->collection->getLastModified()); + } +} diff --git a/lib/private/comments/comment.php b/lib/private/comments/comment.php index 1e1514a587..75964603f9 100644 --- a/lib/private/comments/comment.php +++ b/lib/private/comments/comment.php @@ -284,9 +284,9 @@ class Comment implements IComment { } /** - * returns the timestamp of the most recent child + * returns the DateTime of the most recent child, if set, otherwise null * - * @return int + * @return \DateTime|null * @since 9.0.0 */ public function getLatestChildDateTime() { diff --git a/lib/private/comments/manager.php b/lib/private/comments/manager.php index 64013a9e11..64977c4889 100644 --- a/lib/private/comments/manager.php +++ b/lib/private/comments/manager.php @@ -58,7 +58,9 @@ class Manager implements ICommentsManager { $data['parent_id'] = strval($data['parent_id']); $data['topmost_parent_id'] = strval($data['topmost_parent_id']); $data['creation_timestamp'] = new \DateTime($data['creation_timestamp']); - $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']); + if (!is_null($data['latest_child_timestamp'])) { + $data['latest_child_timestamp'] = new \DateTime($data['latest_child_timestamp']); + } $data['children_count'] = intval($data['children_count']); return $data; }