diff --git a/tests/integration/config/behat.yml b/tests/integration/config/behat.yml index 0c083eca0..8a019df34 100644 --- a/tests/integration/config/behat.yml +++ b/tests/integration/config/behat.yml @@ -5,10 +5,7 @@ default: - '%paths.base%/../features/' contexts: - ServerContext: - baseUrl: http://localhost:8080/index.php/ocs/ - admin: - - admin - - admin - regular_user_password: 123456 - - BoardContext: baseUrl: http://localhost:8080/ + - RequestContext + - BoardContext + - SearchContext diff --git a/tests/integration/features/bootstrap/BoardContext.php b/tests/integration/features/bootstrap/BoardContext.php index 995ec1fd7..cd994151b 100644 --- a/tests/integration/features/bootstrap/BoardContext.php +++ b/tests/integration/features/bootstrap/BoardContext.php @@ -1,6 +1,7 @@ getEnvironment(); + + $this->serverContext = $environment->getContext('ServerContext'); + } + /** * @Given /^creates a board named "([^"]*)" with color "([^"]*)"$/ */ public function createsABoardNamedWithColor($title, $color) { - $this->sendJSONrequest('POST', '/index.php/apps/deck/boards', [ + $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/boards', [ 'title' => $title, 'color' => $color ]); - $this->response->getBody()->seek(0); - $this->board = json_decode((string)$this->response->getBody(), true); + $this->getResponse()->getBody()->seek(0); + $this->board = json_decode((string)$this->getResponse()->getBody(), true); } /** * @When /^fetches the board named "([^"]*)"$/ */ public function fetchesTheBoardNamed($boardName) { - $this->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $this->board['id'], []); - $this->response->getBody()->seek(0); - $this->board = json_decode((string)$this->response->getBody(), true); + $this->requestContext->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $this->board['id'], []); + $this->getResponse()->getBody()->seek(0); + $this->board = json_decode((string)$this->getResponse()->getBody(), true); } /** @@ -48,7 +59,7 @@ class BoardContext implements Context { ]; $tableRows = isset($permissions) ? $permissions->getRowsHash() : []; $result = array_merge($defaults, $tableRows); - $this->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [ + $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [ 'type' => 0, 'participant' => $user, 'permissionEdit' => $result['permissionEdit'] === '1', @@ -68,7 +79,7 @@ class BoardContext implements Context { ]; $tableRows = isset($permissions) ? $permissions->getRowsHash() : []; $result = array_merge($defaults, $tableRows); - $this->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [ + $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [ 'type' => 1, 'participant' => $group, 'permissionEdit' => $result['permissionEdit'] === '1', @@ -82,38 +93,38 @@ class BoardContext implements Context { * @When /^fetching the board list$/ */ public function fetchingTheBoardList() { - $this->sendJSONrequest('GET', '/index.php/apps/deck/boards'); + $this->requestContext->sendJSONrequest('GET', '/index.php/apps/deck/boards'); } /** * @When /^fetching the board with id "([^"]*)"$/ */ public function fetchingTheBoardWithId($id) { - $this->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $id); + $this->requestContext->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $id); } /** * @Given /^create a stack named "([^"]*)"$/ */ public function createAStackNamed($name) { - $this->sendJSONrequest('POST', '/index.php/apps/deck/stacks', [ + $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/stacks', [ 'title' => $name, 'boardId' => $this->board['id'] ]); - $this->response->getBody()->seek(0); - $this->stack = json_decode((string)$this->response->getBody(), true); + $this->requestContext->getResponse()->getBody()->seek(0); + $this->stack = json_decode((string)$this->getResponse()->getBody(), true); } /** * @Given /^create a card named "([^"]*)"$/ */ public function createACardNamed($name) { - $this->sendJSONrequest('POST', '/index.php/apps/deck/cards', [ + $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/cards', [ 'title' => $name, 'stackId' => $this->stack['id'] ]); - $this->response->getBody()->seek(0); - $this->card = json_decode((string)$this->response->getBody(), true); + $this->requestContext->getResponse()->getBody()->seek(0); + $this->card = json_decode((string)$this->getResponse()->getBody(), true); } /** @@ -151,4 +162,70 @@ class BoardContext implements Context { ]); $this->serverContext->creatingShare($table); } + + /** + * @Given /^set the description to "([^"]*)"$/ + */ + public function setTheDescriptionTo($description) { + $this->requestContext->sendJSONrequest('PUT', '/index.php/apps/deck/cards/' . $this->card['id'], array_merge( + $this->card, + ['description' => $description] + )); + $this->requestContext->getResponse()->getBody()->seek(0); + $this->card = json_decode((string)$this->getResponse()->getBody(), true); + } + + /** + * @Given /^set the card attribute "([^"]*)" to "([^"]*)"$/ + */ + public function setCardAttribute($attribute, $value) { + $this->requestContext->sendJSONrequest('PUT', '/index.php/apps/deck/cards/' . $this->card['id'], array_merge( + $this->card, + [$attribute => $value] + )); + $this->requestContext->getResponse()->getBody()->seek(0); + $this->card = json_decode((string)$this->getResponse()->getBody(), true); + } + + /** + * @Given /^set the card duedate to "([^"]*)"$/ + */ + public function setTheCardDuedateTo($arg1) { + $date = new DateTime($arg1); + $this->setCardAttribute('duedate', $date->format(DateTimeInterface::ATOM)); + } + + /** + * @Given /^assign the card to the user "([^"]*)"$/ + */ + public function assignTheCardToTheUser($user) { + $this->assignToCard($user, 0); + } + + /** + * @Given /^assign the card to the group "([^"]*)"$/ + */ + public function assignTheCardToTheGroup($user) { + $this->assignToCard($user, 1); + } + + private function assignToCard($participant, $type) { + $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/cards/' . $this->card['id'] .'/assign', [ + 'userId' => $participant, + 'type' => $type + ]); + $this->requestContext->getResponse()->getBody()->seek(0); + } + + /** + * @Given /^assign the tag "([^"]*)" to the card$/ + */ + public function assignTheTagToTheCard($tag) { + $filteredLabels = array_filter($this->board['labels'], function ($label) use ($tag) { + return $label['title'] === $tag; + }); + $label = array_shift($filteredLabels); + $this->requestContext->sendJSONrequest('POST', '/index.php/apps/deck/cards/' . $this->card['id'] .'/label/' . $label['id']); + $this->requestContext->getResponse()->getBody()->seek(0); + } } diff --git a/tests/integration/features/bootstrap/RequestContext.php b/tests/integration/features/bootstrap/RequestContext.php index 2370758a5..f3aedc8d2 100644 --- a/tests/integration/features/bootstrap/RequestContext.php +++ b/tests/integration/features/bootstrap/RequestContext.php @@ -5,11 +5,12 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use PHPUnit\Framework\Assert; +use Behat\Behat\Context\Context; +use Psr\Http\Message\ResponseInterface; require_once __DIR__ . '/../../vendor/autoload.php'; -trait RequestTrait { - +class RequestContext implements Context { private $response; /** @var ServerContext */ @@ -22,6 +23,9 @@ trait RequestTrait { $this->serverContext = $environment->getContext('ServerContext'); } + private function getBaseUrl() { + } + /** * @Then the response should have a status code :code * @param string $code @@ -90,12 +94,12 @@ trait RequestTrait { } } - private function sendJSONrequest($method, $url, $data = []) { + public function sendJSONrequest($method, $url, $data = []) { $client = new Client; try { $this->response = $client->request( $method, - $this->severContext->getBaseUrl() . $url, + rtrim($this->serverContext->getBaseUrl(), '/') . '/' . ltrim($url, '/'), [ 'cookies' => $this->serverContext->getCookieJar(), 'json' => $data, @@ -109,18 +113,19 @@ trait RequestTrait { } } - private function sendOCSRequest($method, $url, $data = []) { + public function sendOCSRequest($method, $url, $data = []) { $client = new Client; try { $this->response = $client->request( $method, - $this->severContext->getBaseUrl() . $url, + rtrim($this->serverContext->getBaseUrl(), '/') . '/ocs/v2.php/' . ltrim($url, '/'), [ 'cookies' => $this->serverContext->getCookieJar(), 'json' => $data, 'headers' => [ 'requesttoken' => $this->serverContext->getReqestToken(), - 'OCS-APIRequest' => true, + 'OCS-APIREQUEST' => 'true', + 'Accept' => 'application/json' ] ] ); @@ -128,4 +133,8 @@ trait RequestTrait { $this->response = $e->getResponse(); } } + + public function getResponse(): ResponseInterface { + return $this->response; + } } diff --git a/tests/integration/features/bootstrap/RequestTrait.php b/tests/integration/features/bootstrap/RequestTrait.php new file mode 100644 index 000000000..dd8939f62 --- /dev/null +++ b/tests/integration/features/bootstrap/RequestTrait.php @@ -0,0 +1,46 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + + +use Behat\Behat\Hook\Scope\BeforeScenarioScope; + +require_once __DIR__ . '/../../vendor/autoload.php'; + + +trait RequestTrait { + + /** @var RequestContext */ + protected $requestContext; + + /** @BeforeScenario */ + public function gatherRequestTraitContext(BeforeScenarioScope $scope) { + $environment = $scope->getEnvironment(); + $this->requestContext = $environment->getContext('RequestContext'); + } + + public function getResponse() { + return $this->requestContext->getResponse(); + } +} diff --git a/tests/integration/features/bootstrap/SearchContext.php b/tests/integration/features/bootstrap/SearchContext.php new file mode 100644 index 000000000..c65ca6bc6 --- /dev/null +++ b/tests/integration/features/bootstrap/SearchContext.php @@ -0,0 +1,81 @@ +getEnvironment(); + + $this->boardContext = $environment->getContext('BoardContext'); + } + + /** + * @When /^searching for "([^"]*)"$/ + * @param string $term + */ + public function searchingFor(string $term) { + $this->requestContext->sendOCSRequest('GET', '/apps/deck/api/v1.0/search?term=' . urlencode($term), []); + $this->requestContext->getResponse()->getBody()->seek(0); + $data = (string)$this->getResponse()->getBody(); + $this->searchResults = json_decode($data, true); + } + + /** + * @When /^searching for '([^']*)'$/ + * @param string $term + */ + public function searchingForQuotes(string $term) { + $this->searchingFor($term); + } + + /** + * @Then /^the board "([^"]*)" is found$/ + */ + public function theBoardIsFound($arg1) { + $ocsData = $this->searchResults['ocs']['data']; + $found = false; + foreach ($ocsData as $result) { + if ($result['title'] === $arg1) { + $found = true; + } + } + Assert::assertTrue($found, 'Board can be found'); + } + + private function cardIsFound($arg1) { + $ocsData = $this->searchResults['ocs']['data']; + $found = false; + foreach ($ocsData as $result) { + if ($result['title'] === $arg1) { + $found = true; + } + } + return $found; + } + + /** + * @Then /^the card "([^"]*)" is found$/ + */ + public function theCardIsFound($arg1) { + Assert::assertTrue($this->cardIsFound($arg1), 'Card can be found'); + } + + /** + * @Then /^the card "([^"]*)" is not found$/ + */ + public function theCardIsNotFound($arg1) { + Assert::assertFalse($this->cardIsFound($arg1), 'Card can not be found'); + } +} diff --git a/tests/integration/features/bootstrap/ServerContext.php b/tests/integration/features/bootstrap/ServerContext.php index 56a172aa1..e4ed567f1 100644 --- a/tests/integration/features/bootstrap/ServerContext.php +++ b/tests/integration/features/bootstrap/ServerContext.php @@ -6,7 +6,14 @@ use GuzzleHttp\Cookie\CookieJar; require_once __DIR__ . '/../../vendor/autoload.php'; class ServerContext implements Context { - use WebDav; + use WebDav { + WebDav::__construct as private __tConstruct; + } + + public function __construct($baseUrl) { + $this->rawBaseUrl = $baseUrl; + $this->__tConstruct($baseUrl . '/index.php/ocs/', ['admin', 'admin'], '123456'); + } /** @var string */ private $mappedUserId; @@ -28,6 +35,10 @@ class ServerContext implements Context { $this->asAn($user); } + public function getBaseUrl(): string { + return $this->rawBaseUrl; + } + public function getCookieJar(): CookieJar { return $this->cookieJar; } diff --git a/tests/integration/features/decks.feature b/tests/integration/features/decks.feature index f2354209c..bb141ef7d 100644 --- a/tests/integration/features/decks.feature +++ b/tests/integration/features/decks.feature @@ -4,23 +4,6 @@ Feature: decks Given user "admin" exists Given user "user0" exists - Scenario: Request the main frontend page - Given Logging in using web as "admin" - When Sending a "GET" to "/index.php/apps/deck" without requesttoken - Then the HTTP status code should be "200" - - Scenario: Fetch the board list - Given Logging in using web as "admin" - When fetching the board list - Then the response should have a status code "200" - And the response Content-Type should be "application/json; charset=utf-8" - - Scenario: Fetch board details of a nonexisting board - Given Logging in using web as "admin" - When fetching the board with id "99999999" - Then the response should have a status code "403" - And the response Content-Type should be "application/json; charset=utf-8" - Scenario: Create a new board Given Logging in using web as "admin" When creates a board named "MyBoard" with color "000000" diff --git a/tests/integration/features/search.feature b/tests/integration/features/search.feature new file mode 100644 index 000000000..179ffc277 --- /dev/null +++ b/tests/integration/features/search.feature @@ -0,0 +1,259 @@ +Feature: Searching for cards + + Background: + Given user "admin" exists + Given user "user0" exists + Given Logging in using web as "admin" + When creates a board named "MyBoard" with color "000000" + When create a stack named "ToDo" + And create a card named "Example task 1" + And create a card named "Example task 2" + When create a stack named "In progress" + And create a card named "Progress task 1" + And create a card named "Progress task 2" + When create a stack named "Done" + And create a card named "Done task 1" + And set the description to "Done task description 1" + And create a card named "Done task 2" + And set the description to "Done task description 2" + And shares the board with user "user0" + + + Scenario: Search for a card with multiple terms + When searching for "Example task" + Then the card "Example task 1" is found + Then the card "Example task 2" is found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + + Scenario: Search for a card in a specific list + When searching for "task list:Done" + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is found + Then the card "Done task 2" is found + + Scenario: Search for a card with one term + When searching for "task" + Then the card "Example task 1" is found + Then the card "Example task 2" is found + Then the card "Progress task 1" is found + Then the card "Progress task 2" is found + Then the card "Done task 1" is found + Then the card "Done task 2" is found + + Scenario: Search for a card with an differently cased term + When searching for "tAsk" + Then the card "Example task 1" is found + Then the card "Example task 2" is found + Then the card "Progress task 1" is found + Then the card "Progress task 2" is found + Then the card "Done task 1" is found + Then the card "Done task 2" is found + + Scenario: Search for a card title + When searching for 'title:"Done task 1"' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is found + Then the card "Done task 2" is not found + + Scenario: Search for a card description + When searching for 'description:"Done task description"' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is found + Then the card "Done task 2" is found + + Scenario: Search for a non-existing card description + When searching for 'description:"Example"' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + + Scenario: Search on shared boards + Given Logging in using web as "user0" + When searching for "task" + Then the card "Example task 1" is found + Then the card "Example task 2" is found + Then the card "Progress task 1" is found + Then the card "Progress task 2" is found + Then the card "Done task 1" is found + Then the card "Done task 2" is found + + Scenario: Search for a card due date + Given create a card named "Overdue task" + And set the card attribute "duedate" to "2020-12-12" + And create a card named "Future task" + And set the card attribute "duedate" to "3000-12-12" + And create a card named "Tomorrow task" + And set the card duedate to "tomorrow" + When searching for 'date:overdue' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + Then the card "Overdue task" is found + Then the card "Future task" is not found + + Scenario: Search for a card due date + And create a card named "Overdue task" + And set the card attribute "duedate" to "2020-12-12" + And create a card named "Future task" + And set the card attribute "duedate" to "3000-12-12" + And create a card named "Tomorrow task" + And set the card duedate to "+12 hours" + And create a card named "Next week task" + And set the card duedate to "+5 days" + + When searching for 'date:today' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + Then the card "Overdue task" is not found + Then the card "Future task" is not found + Then the card "Tomorrow task" is found + Then the card "Next week task" is not found + + When searching for 'date:week' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + Then the card "Overdue task" is not found + Then the card "Future task" is not found + Then the card "Tomorrow task" is found + Then the card "Next week task" is found + + When searching for 'date:month' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + Then the card "Overdue task" is not found + Then the card "Future task" is not found + Then the card "Tomorrow task" is found + Then the card "Next week task" is found + + When searching for 'date:none' + Then the card "Example task 1" is found + Then the card "Example task 2" is found + Then the card "Progress task 1" is found + Then the card "Progress task 2" is found + Then the card "Done task 1" is found + Then the card "Done task 2" is found + Then the card "Overdue task" is not found + Then the card "Future task" is not found + Then the card "Tomorrow task" is not found + Then the card "Next week task" is not found + + When searching for 'date:<"+7 days"' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + Then the card "Overdue task" is found + Then the card "Future task" is not found + Then the card "Tomorrow task" is found + Then the card "Next week task" is found + + When searching for 'date:>"+10 days"' + Then the card "Example task 1" is not found + Then the card "Example task 2" is not found + Then the card "Progress task 1" is not found + Then the card "Progress task 2" is not found + Then the card "Done task 1" is not found + Then the card "Done task 2" is not found + Then the card "Overdue task" is not found + Then the card "Future task" is found + Then the card "Tomorrow task" is not found + Then the card "Next week task" is not found + + Scenario: Search for assigned user + Given user "user1" exists + And shares the board with user "user1" + Given create a card named "Assigned card to user1" + And assign the card to the user "user1" + When searching for 'assigned:user1' + Then the card "Example task 1" is not found + And the card "Assigned card to user1" is found + + Scenario: Search for assigned user by displayname + Given user "ada" with displayname "Ada Lovelace" exists + And shares the board with user "ada" + Given create a card named "Assigned card to ada" + And assign the card to the user "ada" + When searching for 'assigned:"Ada Lovelace"' + Then the card "Example task 1" is not found + And the card "Assigned card to ada" is found + + Scenario: Search for assigned users + Given user "user1" exists + And shares the board with user "user1" + Given create a card named "Assigned card to user0" + And assign the card to the user "user0" + Given create a card named "Assigned card to user01" + And assign the card to the user "user0" + And assign the card to the user "user1" + When searching for 'assigned:user0 assigned:user1' + Then the card "Example task 1" is not found + And the card "Assigned card to user0" is not found + And the card "Assigned card to user01" is found + + Scenario: Search for assigned group + Given user "user1" exists + And shares the board with user "user1" + Given group "group1" exists + And shares the board with group "group1" + Given user "user1" belongs to group "group1" + Given create a card named "Assigned card to group1" + And assign the card to the group "group1" + When searching for 'assigned:user1' + Then the card "Example task 1" is not found + And the card "Assigned card to group1" is found + + When searching for 'assigned:group1' + Then the card "Example task 1" is not found + And the card "Assigned card to group1" is found + + Scenario: Search for assigned tag + Given create a card named "Labeled card" + # Default labels from boards are used for this test case + And assign the tag "Finished" to the card + When searching for 'tag:Finished' + Then the card "Example task 1" is not found + And the card "Labeled card" is found + + Given create a card named "Multi labeled card" + And assign the tag "Finished" to the card + And assign the tag "To review" to the card + When searching for 'tag:Finished tag:Later' + Then the card "Example task 1" is not found + And the card "Multi labeled card" is not found + + When searching for 'tag:Finished tag:"To review"' + Then the card "Example task 1" is not found + And the card "Labeled card" is not found + And the card "Multi labeled card" is found