diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml index 42f5f88ac8..a87d4fe44c 100644 --- a/build/integration/config/behat.yml +++ b/build/integration/config/behat.yml @@ -12,6 +12,8 @@ default: - admin - admin regular_user_password: 123456 + - CommentsContext: + baseUrl: http://localhost:8080 federation: paths: - %paths.base%/../federation_features diff --git a/build/integration/features/bootstrap/CommentsContext.php b/build/integration/features/bootstrap/CommentsContext.php new file mode 100644 index 0000000000..d720bb8dcd --- /dev/null +++ b/build/integration/features/bootstrap/CommentsContext.php @@ -0,0 +1,263 @@ + + * + * @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 + * + */ + +require __DIR__ . '/../../vendor/autoload.php'; + +class CommentsContext implements \Behat\Behat\Context\Context { + /** @var string */ + private $baseUrl; + /** @var array */ + private $response; + /** @var int */ + private $commentId; + /** @var int */ + private $fileId; + + /** + * @param string $baseUrl + */ + public function __construct($baseUrl) { + $this->baseUrl = $baseUrl; + + // in case of ci deployment we take the server url from the environment + $testServerUrl = getenv('TEST_SERVER_URL'); + if ($testServerUrl !== false) { + $this->baseUrl = substr($testServerUrl, 0, -5); + } + } + + /** @AfterScenario */ + public function teardownScenario(\Behat\Behat\Hook\Scope\AfterScenarioScope $scope) { + $client = new \GuzzleHttp\Client(); + try { + $client->delete( + $this->baseUrl.'/remote.php/webdav/myFileToComment.txt', + [ + 'auth' => [ + 'user0', + '123456', + ], + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ] + ); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $e->getResponse(); + } + } + + /** + * @return int + */ + private function getFileIdForPath($path) { + $url = $this->baseUrl.'/remote.php/webdav/'.$path; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'PROPFIND', + 'header' => "Authorization: Basic dXNlcjA6MTIzNDU2\r\nContent-Type: application/x-www-form-urlencoded", + 'content' => ' + + + + +' + ) + )); + + $response = file_get_contents($url, false, $context); + preg_match_all('/\(.*)\<\/oc:fileid\>/', $response, $matches); + return (int)$matches[1][0]; + } + + /** + * @When :user posts a comment with content :content on the file named :fileName it should return :statusCode + */ + public function postsACommentWithContentOnTheFileNamedItShouldReturn($user, $content, $fileName, $statusCode) { + $fileId = $this->getFileIdForPath($fileName); + $this->fileId = (int)$fileId; + $url = $this->baseUrl.'/remote.php/dav/comments/files/'.$fileId.'/'; + + $client = new \GuzzleHttp\Client(); + try { + $res = $client->post( + $url, + [ + 'body' => '{"actorId":"user0","actorDisplayName":"user0","actorType":"users","verb":"comment","message":"' . $content . '","creationDateTime":"Thu, 18 Feb 2016 17:04:18 GMT","objectType":"files"}', + 'auth' => [ + $user, + '123456', + ], + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ] + ); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $res = $e->getResponse(); + } + + if($res->getStatusCode() !== (int)$statusCode) { + throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")"); + } + } + + + /** + * @Then As :user load all the comments of the file named :fileName it should return :statusCode + */ + public function asLoadloadAllTheCommentsOfTheFileNamedItShouldReturn($user, $fileName, $statusCode) { + $fileId = $this->getFileIdForPath($fileName); + $url = $this->baseUrl.'/remote.php/dav/comments/files/'.$fileId.'/'; + + try { + $client = new \GuzzleHttp\Client(); + $res = $client->createRequest( + 'REPORT', + $url, + [ + 'body' => ' + + 200 + 0 + +', + 'auth' => [ + $user, + '123456', + ], + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ] + ); + $res = $client->send($res); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $res = $e->getResponse(); + } + + if($res->getStatusCode() !== (int)$statusCode) { + throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")"); + } + + if($res->getStatusCode() === 207) { + $service = new Sabre\Xml\Service(); + $this->response = $service->parse($res->getBody()->getContents()); + $this->commentId = (int)$this->response[0]['value'][2]['value'][0]['value'][0]['value']; + } + } + + /** + * @Given As :user sending :verb to :url with + */ + public function asUserSendingToWith($user, $verb, $url, \Behat\Gherkin\Node\TableNode $body) { + $client = new \GuzzleHttp\Client(); + $options = []; + $options['auth'] = [$user, '123456']; + $fd = $body->getRowsHash(); + $options['body'] = $fd; + $client->send($client->createRequest($verb, $this->baseUrl.'/ocs/v1.php/'.$url, $options)); + } + + /** + * @Then As :user delete the created comment it should return :statusCode + */ + public function asDeleteTheCreatedCommentItShouldReturn($user, $statusCode) { + $url = $this->baseUrl.'/remote.php/dav/comments/files/'.$this->fileId.'/'.$this->commentId; + + $client = new \GuzzleHttp\Client(); + try { + $res = $client->delete( + $url, + [ + 'auth' => [ + $user, + '123456', + ], + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ] + ); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $res = $e->getResponse(); + } + + if($res->getStatusCode() !== (int)$statusCode) { + throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")"); + } + } + + /** + * @Then the response should contain a property :key with value :value + */ + public function theResponseShouldContainAPropertyWithValue($key, $value) { + $keys = $this->response[0]['value'][2]['value'][0]['value']; + $found = false; + foreach($keys as $singleKey) { + if($singleKey['name'] === '{http://owncloud.org/ns}'.substr($key, 3)) { + if($singleKey['value'] === $value) { + $found = true; + } + } + } + if($found === false) { + throw new \Exception("Cannot find property $key with $value"); + } + } + + /** + * @Then the response should contain only :number comments + */ + public function theResponseShouldContainOnlyComments($number) { + if(count($this->response) !== (int)$number) { + throw new \Exception("Found more comments than $number (".count($this->response).")"); + } + } + + /** + * @Then As :user edit the last created comment and set text to :text it should return :statusCode + */ + public function asEditTheLastCreatedCommentAndSetTextToItShouldReturn($user, $text, $statusCode) { + $client = new \GuzzleHttp\Client(); + $options = []; + $options['auth'] = [$user, '123456']; + $options['body'] = ' + + + + '.$text.' + + +'; + try { + $res = $client->send($client->createRequest('PROPPATCH', $this->baseUrl.'/remote.php/dav/comments/files/' . $this->fileId . '/' . $this->commentId, $options)); + } catch (\GuzzleHttp\Exception\ClientException $e) { + $res = $e->getResponse(); + } + + if($res->getStatusCode() !== (int)$statusCode) { + throw new \Exception("Response status code was not $statusCode (".$res->getStatusCode().")"); + } + } + + +} diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php index 5103b4af50..faf8e0bf50 100644 --- a/build/integration/features/bootstrap/Sharing.php +++ b/build/integration/features/bootstrap/Sharing.php @@ -370,5 +370,49 @@ trait Sharing{ } } + /** + * @Then As :user remove all shares from the file named :fileName + */ + public function asRemoveAllSharesFromTheFileNamed($user, $fileName) { + $url = $this->baseUrl.'v2.php/apps/files_sharing/api/v1/shares?format=json'; + $client = new \GuzzleHttp\Client(); + $res = $client->get( + $url, + [ + 'auth' => [ + $user, + '123456', + ], + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ] + ); + $json = json_decode($res->getBody()->getContents(), true); + $deleted = false; + foreach($json['ocs']['data'] as $data) { + if (stripslashes($data['path']) === $fileName) { + $id = $data['id']; + $client->delete( + $this->baseUrl.'v2.php/apps/files_sharing/api/v1/shares/'.$id, + [ + 'auth' => [ + $user, + '123456', + ], + 'headers' => [ + 'Content-Type' => 'application/json', + ], + ] + ); + $deleted = true; + } + } + + if($deleted === false) { + throw new \Exception("Could not delete file $fileName"); + } + } + } diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php index 49cd565cf2..58fdfed171 100644 --- a/build/integration/features/bootstrap/WebDav.php +++ b/build/integration/features/bootstrap/WebDav.php @@ -9,8 +9,7 @@ use Sabre\DAV\Client as SClient; require __DIR__ . '/../../vendor/autoload.php'; -trait WebDav{ - +trait WebDav { /** @var string*/ private $davPath = "remote.php/webdav"; @@ -162,6 +161,18 @@ trait WebDav{ } } + /** + * @When User :user deletes file :file + */ + public function userDeletesFile($user, $file) { + try { + $this->response = $this->makeDavRequest($user, 'DELETE', $file, []); + } catch (\GuzzleHttp\Exception\ServerException $e) { + // 4xx and 5xx responses cause an exception + $this->response = $e->getResponse(); + } + } + /** * @Given User :user created a folder :destination */ diff --git a/build/integration/features/comments.feature b/build/integration/features/comments.feature new file mode 100644 index 0000000000..135bb01652 --- /dev/null +++ b/build/integration/features/comments.feature @@ -0,0 +1,209 @@ +Feature: comments + Scenario: Creating a comment on a file belonging to myself + Given user "user0" exists + Given As an "user0" + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + When "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user0" + And the response should contain only "1" comments + + Scenario: Creating a comment on a shared file belonging to another user + Given user "user0" exists + Given user "user1" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with + | path | myFileToComment.txt | + | shareWith | user1 | + | shareType | 0 | + When "user1" posts a comment with content "A comment from another user" on the file named "/myFileToComment.txt" it should return "201" + Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "A comment from another user" + And the response should contain a property "oc:actorDisplayName" with value "user1" + And the response should contain only "1" comments + + Scenario: Creating a comment on a non-shared file belonging to another user + Given user "user0" exists + Given user "user1" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Then "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "404" + + Scenario: Reading comments on a non-shared file belonging to another user + Given user "user0" exists + Given user "user1" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "404" + + Scenario: Deleting my own comments on a file belonging to myself + Given user "user0" exists + Given As an "user0" + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + Then the response should contain a property "oc:parentId" with value "0" + Then the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user0" + And the response should contain only "1" comments + And As "user0" delete the created comment it should return "204" + And As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain only "0" comments + + Scenario: Deleting my own comments on a file shared by somebody else + Given user "user0" exists + Given user "user1" exists + Given As an "user0" + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with + | path | myFileToComment.txt | + | shareWith | user1 | + | shareType | 0 | + Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + When As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207" + Then the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user1" + And the response should contain only "1" comments + And As "user1" delete the created comment it should return "204" + And As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain only "0" comments + + Scenario: Deleting my own comments on a file unshared by someone else + Given user "user0" exists + Given user "user1" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with + | path | myFileToComment.txt | + | shareWith | user1 | + | shareType | 0 | + Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + When As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207" + Then the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user1" + And the response should contain only "1" comments + And As "user0" remove all shares from the file named "/myFileToComment.txt" + And As "user1" delete the created comment it should return "404" + And As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "404" + + Scenario: Edit my own comments on a file belonging to myself + Given user "user0" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given "user0" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + Then the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user0" + And the response should contain only "1" comments + When As "user0" edit the last created comment and set text to "My edited comment" it should return "207" + Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My edited comment" + And the response should contain a property "oc:actorDisplayName" with value "user0" + + Scenario: Edit my own comments on a file shared by someone with me + Given user "user0" exists + Given user "user1" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with + | path | myFileToComment.txt | + | shareWith | user1 | + | shareType | 0 | + Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + Then the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user1" + And the response should contain only "1" comments + Given As "user1" edit the last created comment and set text to "My edited comment" it should return "207" + Then As "user1" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My edited comment" + And the response should contain a property "oc:actorDisplayName" with value "user1" + + Scenario: Edit my own comments on a file unshared by someone with me + Given user "user0" exists + Given user "user1" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with + | path | myFileToComment.txt | + | shareWith | user1 | + | shareType | 0 | + When "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user1" + And the response should contain only "1" comments + And As "user0" remove all shares from the file named "/myFileToComment.txt" + When As "user1" edit the last created comment and set text to "My edited comment" it should return "404" + Then As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + And the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user1" + + Scenario: Edit comments of other users should not be possible + Given user "user0" exists + Given user "user1" exists + Given User "user0" uploads file "data/textfile.txt" to "/myFileToComment.txt" + Given As "user0" sending "POST" to "/apps/files_sharing/api/v1/shares" with + | path | myFileToComment.txt | + | shareWith | user1 | + | shareType | 0 | + Given "user1" posts a comment with content "My first comment" on the file named "/myFileToComment.txt" it should return "201" + When As "user0" load all the comments of the file named "/myFileToComment.txt" it should return "207" + Then the response should contain a property "oc:parentId" with value "0" + And the response should contain a property "oc:childrenCount" with value "0" + And the response should contain a property "oc:verb" with value "comment" + And the response should contain a property "oc:actorType" with value "users" + And the response should contain a property "oc:objectType" with value "files" + And the response should contain a property "oc:message" with value "My first comment" + And the response should contain a property "oc:actorDisplayName" with value "user1" + And the response should contain only "1" comments + Then As "user0" edit the last created comment and set text to "My edited comment" it should return "403" \ No newline at end of file