Merge pull request #65 from nextcloud/test-behat
Add integration tests with behat
This commit is contained in:
22
.drone.yml
22
.drone.yml
@@ -125,10 +125,29 @@ pipeline:
|
|||||||
- cd ../server/
|
- cd ../server/
|
||||||
- php occ app:enable deck
|
- php occ app:enable deck
|
||||||
- cd apps/$APP_NAME
|
- cd apps/$APP_NAME
|
||||||
- make test
|
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
|
||||||
|
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
||||||
when:
|
when:
|
||||||
matrix:
|
matrix:
|
||||||
TESTS: php7.1
|
TESTS: php7.1
|
||||||
|
integration:
|
||||||
|
image: nextcloudci/integration-php7.0:integration-php7.0-3
|
||||||
|
environment:
|
||||||
|
- APP_NAME=deck
|
||||||
|
- CORE_BRANCH=master
|
||||||
|
- DB=sqlite
|
||||||
|
commands:
|
||||||
|
# Pre-setup steps
|
||||||
|
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
|
||||||
|
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
|
||||||
|
- cd ../server/
|
||||||
|
- php occ app:enable deck
|
||||||
|
- cd apps/$APP_NAME
|
||||||
|
- cd tests/integration
|
||||||
|
- ./run.sh
|
||||||
|
when:
|
||||||
|
matrix:
|
||||||
|
TESTS: integration
|
||||||
jsbuild:
|
jsbuild:
|
||||||
image: mhart/alpine-node:6.8.0
|
image: mhart/alpine-node:6.8.0
|
||||||
commands:
|
commands:
|
||||||
@@ -149,5 +168,6 @@ matrix:
|
|||||||
- TESTS: php7.0
|
- TESTS: php7.0
|
||||||
- TESTS: php7.1
|
- TESTS: php7.1
|
||||||
- TESTS: jsbuild
|
- TESTS: jsbuild
|
||||||
|
- TESTS: integration
|
||||||
|
|
||||||
branches: [ master, stable* ]
|
branches: [ master, stable* ]
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@ js/node_modules/*
|
|||||||
js/vendor/
|
js/vendor/
|
||||||
build/
|
build/
|
||||||
js/public/
|
js/public/
|
||||||
|
tests/integration/vendor/
|
||||||
|
tests/integration/composer.lock
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ before_script:
|
|||||||
- cd apps/deck
|
- cd apps/deck
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make test
|
- make test-unit
|
||||||
|
|
||||||
after_failure:
|
after_failure:
|
||||||
- cat ../../data/nextcloud.log
|
- cat ../../data/nextcloud.log
|
||||||
|
|||||||
10
Makefile
10
Makefile
@@ -69,8 +69,9 @@ appstore: clean-build build
|
|||||||
|
|
||||||
echo $(appstore_package_name).tar.gz
|
echo $(appstore_package_name).tar.gz
|
||||||
|
|
||||||
|
test: test-unit test-integration
|
||||||
|
|
||||||
test:
|
test-unit:
|
||||||
mkdir -p build/
|
mkdir -p build/
|
||||||
ifeq (, $(shell which phpunit 2> /dev/null))
|
ifeq (, $(shell which phpunit 2> /dev/null))
|
||||||
@echo "No phpunit command available, downloading a copy from the web"
|
@echo "No phpunit command available, downloading a copy from the web"
|
||||||
@@ -83,6 +84,9 @@ else
|
|||||||
phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
|
||||||
endif
|
endif
|
||||||
|
|
||||||
test-js:
|
test-integration:
|
||||||
cd js && run test
|
cd tests/integration && ./run.sh
|
||||||
|
|
||||||
|
test-js: install-deps
|
||||||
|
cd js && run test
|
||||||
|
|
||||||
|
|||||||
23
tests/integration/composer.json
Normal file
23
tests/integration/composer.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~4.6",
|
||||||
|
"behat/behat": "^3.0",
|
||||||
|
"guzzlehttp/guzzle": "~5.0",
|
||||||
|
"jarnaiz/behat-junit-formatter": "^1.3",
|
||||||
|
"sabre/dav": "3.2"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"../../../../build/integration/features/bootstrap/Auth.php",
|
||||||
|
"../../../../build/integration/features/bootstrap/Provisioning.php",
|
||||||
|
"../../../../build/integration/features/bootstrap/Sharing.php",
|
||||||
|
"../../../../build/integration/features/bootstrap/Trashbin.php",
|
||||||
|
"../../../../build/integration/features/bootstrap/WebDav.php"
|
||||||
|
],
|
||||||
|
"psr-0": {
|
||||||
|
"": [
|
||||||
|
"features/bootstrap/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
tests/integration/config/behat.yml
Normal file
12
tests/integration/config/behat.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
default:
|
||||||
|
suites:
|
||||||
|
test:
|
||||||
|
paths:
|
||||||
|
- %paths.base%/../features/
|
||||||
|
contexts:
|
||||||
|
- FeatureContext:
|
||||||
|
baseUrl: http://localhost:8080/index.php/ocs/
|
||||||
|
admin:
|
||||||
|
- admin
|
||||||
|
- admin
|
||||||
|
regular_user_password: 123456
|
||||||
36
tests/integration/features/acl.feature
Normal file
36
tests/integration/features/acl.feature
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
Feature: acl
|
||||||
|
Routes should check for permissions when a user sends a requests
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given user "admin" exists
|
||||||
|
And user "user0" exists
|
||||||
|
And user "user1" exists
|
||||||
|
And user "user2" exists
|
||||||
|
Given group "group0" exists
|
||||||
|
And group "group1" exists
|
||||||
|
Given user "user1" belongs to group "group1"
|
||||||
|
|
||||||
|
Scenario: Request the main frontend page
|
||||||
|
Given Logging in using web as "user0"
|
||||||
|
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 "user0"
|
||||||
|
When Sending a "GET" to "/index.php/apps/deck/boards" with requesttoken
|
||||||
|
Then the HTTP status code should be "200"
|
||||||
|
And the Content-Type should be "application/json; charset=utf-8"
|
||||||
|
|
||||||
|
Scenario: Fetch board details of owned board
|
||||||
|
Given Logging in using web as "admin"
|
||||||
|
And creates a board named "MyPrivateAdminBoard" with color "fafafa"
|
||||||
|
When "admin" fetches the board named "MyPrivateAdminBoard"
|
||||||
|
Then the HTTP status code should be "200"
|
||||||
|
And the Content-Type should be "application/json; charset=utf-8"
|
||||||
|
|
||||||
|
Scenario: Fetch board details of an other users board
|
||||||
|
Given Logging in using web as "admin"
|
||||||
|
And creates a board named "MyPrivateAdminBoard" with color "fafafa"
|
||||||
|
When "user0" fetches the board named "MyPrivateAdminBoard"
|
||||||
|
Then the HTTP status code should be "403"
|
||||||
|
And the Content-Type should be "application/json; charset=utf-8"
|
||||||
450
tests/integration/features/bootstrap/BasicStructure.php
Normal file
450
tests/integration/features/bootstrap/BasicStructure.php
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Christoph Wurst <christoph@owncloud.com>
|
||||||
|
* @author Joas Schilling <coding@schilljs.com>
|
||||||
|
* @author Lukas Reschke <lukas@statuscode.ch>
|
||||||
|
* @author Sergio Bertolin <sbertolin@solidgear.es>
|
||||||
|
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||||
|
*
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Cookie\CookieJar;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use GuzzleHttp\Message\ResponseInterface;
|
||||||
|
|
||||||
|
require __DIR__ . '/../../vendor/autoload.php';
|
||||||
|
|
||||||
|
trait BasicStructure {
|
||||||
|
|
||||||
|
use Auth;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $currentUser = '';
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $currentServer = '';
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $baseUrl = '';
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $apiVersion = 1;
|
||||||
|
|
||||||
|
/** @var ResponseInterface */
|
||||||
|
private $response = null;
|
||||||
|
|
||||||
|
/** @var CookieJar */
|
||||||
|
private $cookieJar;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $requestToken;
|
||||||
|
|
||||||
|
public function __construct($baseUrl, $admin, $regular_user_password) {
|
||||||
|
|
||||||
|
// Initialize your context here
|
||||||
|
$this->baseUrl = $baseUrl;
|
||||||
|
$this->adminUser = $admin;
|
||||||
|
$this->regularUser = $regular_user_password;
|
||||||
|
$this->localBaseUrl = $this->baseUrl;
|
||||||
|
$this->remoteBaseUrl = $this->baseUrl;
|
||||||
|
$this->currentServer = 'LOCAL';
|
||||||
|
$this->cookieJar = new CookieJar();
|
||||||
|
|
||||||
|
// in case of ci deployment we take the server url from the environment
|
||||||
|
$testServerUrl = getenv('TEST_SERVER_URL');
|
||||||
|
if ($testServerUrl !== false) {
|
||||||
|
$this->baseUrl = $testServerUrl;
|
||||||
|
$this->localBaseUrl = $testServerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// federated server url from the environment
|
||||||
|
$testRemoteServerUrl = getenv('TEST_SERVER_FED_URL');
|
||||||
|
if ($testRemoteServerUrl !== false) {
|
||||||
|
$this->remoteBaseUrl = $testRemoteServerUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^using api version "(\d+)"$/
|
||||||
|
* @param string $version
|
||||||
|
*/
|
||||||
|
public function usingApiVersion($version) {
|
||||||
|
$this->apiVersion = (int) $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^As an "([^"]*)"$/
|
||||||
|
* @param string $user
|
||||||
|
*/
|
||||||
|
public function asAn($user) {
|
||||||
|
$this->currentUser = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^Using server "(LOCAL|REMOTE)"$/
|
||||||
|
* @param string $server
|
||||||
|
* @return string Previous used server
|
||||||
|
*/
|
||||||
|
public function usingServer($server) {
|
||||||
|
$previousServer = $this->currentServer;
|
||||||
|
if ($server === 'LOCAL'){
|
||||||
|
$this->baseUrl = $this->localBaseUrl;
|
||||||
|
$this->currentServer = 'LOCAL';
|
||||||
|
return $previousServer;
|
||||||
|
} else {
|
||||||
|
$this->baseUrl = $this->remoteBaseUrl;
|
||||||
|
$this->currentServer = 'REMOTE';
|
||||||
|
return $previousServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When /^sending "([^"]*)" to "([^"]*)"$/
|
||||||
|
* @param string $verb
|
||||||
|
* @param string $url
|
||||||
|
*/
|
||||||
|
public function sendingTo($verb, $url) {
|
||||||
|
$this->sendingToWith($verb, $url, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the xml answer to get ocs response which doesn't match with
|
||||||
|
* http one in v1 of the api.
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOCSResponse($response) {
|
||||||
|
return $response->xml()->meta[0]->statuscode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is needed to use a vertical fashion in the gherkin tables.
|
||||||
|
* @param array $arrayOfArrays
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function simplifyArray($arrayOfArrays){
|
||||||
|
$a = array_map(function($subArray) { return $subArray[0]; }, $arrayOfArrays);
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When /^sending "([^"]*)" to "([^"]*)" with$/
|
||||||
|
* @param string $verb
|
||||||
|
* @param string $url
|
||||||
|
* @param \Behat\Gherkin\Node\TableNode $body
|
||||||
|
*/
|
||||||
|
public function sendingToWith($verb, $url, $body) {
|
||||||
|
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php" . $url;
|
||||||
|
$client = new Client();
|
||||||
|
$options = [];
|
||||||
|
if ($this->currentUser === 'admin') {
|
||||||
|
$options['auth'] = $this->adminUser;
|
||||||
|
} else {
|
||||||
|
$options['auth'] = [$this->currentUser, $this->regularUser];
|
||||||
|
}
|
||||||
|
$options['headers'] = [
|
||||||
|
'OCS_APIREQUEST' => 'true'
|
||||||
|
];
|
||||||
|
if ($body instanceof \Behat\Gherkin\Node\TableNode) {
|
||||||
|
$fd = $body->getRowsHash();
|
||||||
|
$options['body'] = $fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix this hack!
|
||||||
|
if ($verb === 'PUT' && $body === null) {
|
||||||
|
$options['body'] = [
|
||||||
|
'foo' => 'bar',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->response = $client->send($client->createRequest($verb, $fullUrl, $options));
|
||||||
|
} catch (ClientException $ex) {
|
||||||
|
$this->response = $ex->getResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When /^sending "([^"]*)" with exact url to "([^"]*)"$/
|
||||||
|
* @param string $verb
|
||||||
|
* @param string $url
|
||||||
|
*/
|
||||||
|
public function sendingToDirectUrl($verb, $url) {
|
||||||
|
$this->sendingToWithDirectUrl($verb, $url, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendingToWithDirectUrl($verb, $url, $body) {
|
||||||
|
$fullUrl = substr($this->baseUrl, 0, -5) . $url;
|
||||||
|
$client = new Client();
|
||||||
|
$options = [];
|
||||||
|
if ($this->currentUser === 'admin') {
|
||||||
|
$options['auth'] = $this->adminUser;
|
||||||
|
} else {
|
||||||
|
$options['auth'] = [$this->currentUser, $this->regularUser];
|
||||||
|
}
|
||||||
|
if ($body instanceof \Behat\Gherkin\Node\TableNode) {
|
||||||
|
$fd = $body->getRowsHash();
|
||||||
|
$options['body'] = $fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->response = $client->send($client->createRequest($verb, $fullUrl, $options));
|
||||||
|
} catch (ClientException $ex) {
|
||||||
|
$this->response = $ex->getResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExpectedUrl($possibleUrl, $finalPart){
|
||||||
|
$baseUrlChopped = substr($this->baseUrl, 0, -4);
|
||||||
|
$endCharacter = strlen($baseUrlChopped) + strlen($finalPart);
|
||||||
|
return (substr($possibleUrl,0,$endCharacter) == "$baseUrlChopped" . "$finalPart");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then /^the OCS status code should be "([^"]*)"$/
|
||||||
|
* @param int $statusCode
|
||||||
|
*/
|
||||||
|
public function theOCSStatusCodeShouldBe($statusCode) {
|
||||||
|
PHPUnit_Framework_Assert::assertEquals($statusCode, $this->getOCSResponse($this->response));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then /^the HTTP status code should be "([^"]*)"$/
|
||||||
|
* @param int $statusCode
|
||||||
|
*/
|
||||||
|
public function theHTTPStatusCodeShouldBe($statusCode) {
|
||||||
|
PHPUnit_Framework_Assert::assertEquals($statusCode, $this->response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then /^the Content-Type should be "([^"]*)"$/
|
||||||
|
* @param string $contentType
|
||||||
|
*/
|
||||||
|
public function theContentTypeShouldbe($contentType) {
|
||||||
|
PHPUnit_Framework_Assert::assertEquals($contentType, $this->response->getHeader('Content-Type'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
*/
|
||||||
|
private function extracRequestTokenFromResponse(ResponseInterface $response) {
|
||||||
|
$this->requestToken = substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given Logging in using web as :user
|
||||||
|
* @param string $user
|
||||||
|
*/
|
||||||
|
public function loggingInUsingWebAs($user) {
|
||||||
|
$loginUrl = substr($this->baseUrl, 0, -5) . '/login';
|
||||||
|
// Request a new session and extract CSRF token
|
||||||
|
$client = new Client();
|
||||||
|
$response = $client->get(
|
||||||
|
$loginUrl,
|
||||||
|
[
|
||||||
|
'cookies' => $this->cookieJar,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->extracRequestTokenFromResponse($response);
|
||||||
|
|
||||||
|
// Login and extract new token
|
||||||
|
$password = ($user === 'admin') ? 'admin' : '123456';
|
||||||
|
$client = new Client();
|
||||||
|
$response = $client->post(
|
||||||
|
$loginUrl,
|
||||||
|
[
|
||||||
|
'body' => [
|
||||||
|
'user' => $user,
|
||||||
|
'password' => $password,
|
||||||
|
'requesttoken' => $this->requestToken,
|
||||||
|
],
|
||||||
|
'cookies' => $this->cookieJar,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->extracRequestTokenFromResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When Sending a :method to :url with requesttoken
|
||||||
|
* @param string $method
|
||||||
|
* @param string $url
|
||||||
|
*/
|
||||||
|
public function sendingAToWithRequesttoken($method, $url) {
|
||||||
|
$baseUrl = substr($this->baseUrl, 0, -5);
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
$request = $client->createRequest(
|
||||||
|
$method,
|
||||||
|
$baseUrl . $url,
|
||||||
|
[
|
||||||
|
'cookies' => $this->cookieJar,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$request->addHeader('requesttoken', $this->requestToken);
|
||||||
|
try {
|
||||||
|
$this->response = $client->send($request);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
$this->response = $e->getResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When Sending a :method to :url without requesttoken
|
||||||
|
* @param string $method
|
||||||
|
* @param string $url
|
||||||
|
*/
|
||||||
|
public function sendingAToWithoutRequesttoken($method, $url) {
|
||||||
|
$baseUrl = substr($this->baseUrl, 0, -5);
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
$request = $client->createRequest(
|
||||||
|
$method,
|
||||||
|
$baseUrl . $url,
|
||||||
|
[
|
||||||
|
'cookies' => $this->cookieJar,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
$this->response = $client->send($request);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
$this->response = $e->getResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function removeFile($path, $filename){
|
||||||
|
if (file_exists("$path" . "$filename")) {
|
||||||
|
unlink("$path" . "$filename");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given User :user modifies text of :filename with text :text
|
||||||
|
* @param string $user
|
||||||
|
* @param string $filename
|
||||||
|
* @param string $text
|
||||||
|
*/
|
||||||
|
public function modifyTextOfFile($user, $filename, $text) {
|
||||||
|
self::removeFile("../../data/$user/files", "$filename");
|
||||||
|
file_put_contents("../../data/$user/files" . "$filename", "$text");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFileSpecificSize($name, $size) {
|
||||||
|
$file = fopen("work/" . "$name", 'w');
|
||||||
|
fseek($file, $size - 1 ,SEEK_CUR);
|
||||||
|
fwrite($file,'a'); // write a dummy char at SIZE position
|
||||||
|
fclose($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFileWithText($name, $text){
|
||||||
|
$file = fopen("work/" . "$name", 'w');
|
||||||
|
fwrite($file, $text);
|
||||||
|
fclose($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given file :filename of size :size is created in local storage
|
||||||
|
* @param string $filename
|
||||||
|
* @param string $size
|
||||||
|
*/
|
||||||
|
public function fileIsCreatedInLocalStorageWithSize($filename, $size) {
|
||||||
|
$this->createFileSpecificSize("local_storage/$filename", $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given file :filename with text :text is created in local storage
|
||||||
|
* @param string $filename
|
||||||
|
* @param string $text
|
||||||
|
*/
|
||||||
|
public function fileIsCreatedInLocalStorageWithText($filename, $text) {
|
||||||
|
$this->createFileWithText("local_storage/$filename", $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When Sleep for :seconds seconds
|
||||||
|
* @param int $seconds
|
||||||
|
*/
|
||||||
|
public function sleepForSeconds($seconds) {
|
||||||
|
sleep((int)$seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeSuite
|
||||||
|
*/
|
||||||
|
public static function addFilesToSkeleton(){
|
||||||
|
for ($i=0; $i<5; $i++){
|
||||||
|
file_put_contents("../../core/skeleton/" . "textfile" . "$i" . ".txt", "Nextcloud test text file\n");
|
||||||
|
}
|
||||||
|
if (!file_exists("../../core/skeleton/FOLDER")) {
|
||||||
|
mkdir("../../core/skeleton/FOLDER", 0777, true);
|
||||||
|
}
|
||||||
|
if (!file_exists("../../core/skeleton/PARENT")) {
|
||||||
|
mkdir("../../core/skeleton/PARENT", 0777, true);
|
||||||
|
}
|
||||||
|
file_put_contents("../../core/skeleton/PARENT/" . "parent.txt", "Nextcloud test text file\n");
|
||||||
|
if (!file_exists("../../core/skeleton/PARENT/CHILD")) {
|
||||||
|
mkdir("../../core/skeleton/PARENT/CHILD", 0777, true);
|
||||||
|
}
|
||||||
|
file_put_contents("../../core/skeleton/PARENT/CHILD/" . "child.txt", "Nextcloud test text file\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @AfterSuite
|
||||||
|
*/
|
||||||
|
public static function removeFilesFromSkeleton(){
|
||||||
|
for ($i=0; $i<5; $i++){
|
||||||
|
self::removeFile("../../core/skeleton/", "textfile" . "$i" . ".txt");
|
||||||
|
}
|
||||||
|
if (is_dir("../../core/skeleton/FOLDER")) {
|
||||||
|
rmdir("../../core/skeleton/FOLDER");
|
||||||
|
}
|
||||||
|
self::removeFile("../../core/skeleton/PARENT/CHILD/", "child.txt");
|
||||||
|
if (is_dir("../../core/skeleton/PARENT/CHILD")) {
|
||||||
|
rmdir("../../core/skeleton/PARENT/CHILD");
|
||||||
|
}
|
||||||
|
self::removeFile("../../core/skeleton/PARENT/", "parent.txt");
|
||||||
|
if (is_dir("../../core/skeleton/PARENT")) {
|
||||||
|
rmdir("../../core/skeleton/PARENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeScenario @local_storage
|
||||||
|
*/
|
||||||
|
public static function removeFilesFromLocalStorageBefore(){
|
||||||
|
$dir = "./work/local_storage/";
|
||||||
|
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
|
||||||
|
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
foreach ( $ri as $file ) {
|
||||||
|
$file->isDir() ? rmdir($file) : unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @AfterScenario @local_storage
|
||||||
|
*/
|
||||||
|
public static function removeFilesFromLocalStorageAfter(){
|
||||||
|
$dir = "./work/local_storage/";
|
||||||
|
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
|
||||||
|
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
foreach ( $ri as $file ) {
|
||||||
|
$file->isDir() ? rmdir($file) : unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
194
tests/integration/features/bootstrap/FeatureContext.php
Normal file
194
tests/integration/features/bootstrap/FeatureContext.php
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Behat\Behat\Context\Context;
|
||||||
|
use Behat\Gherkin\Node\TableNode;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use Behat\Behat\Tester\Exception\PendingException;
|
||||||
|
use Behat\Gherkin\Node\PyStringNode;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||||
|
|
||||||
|
class FeatureContext implements Context {
|
||||||
|
|
||||||
|
use WebDav;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $mappedUserId;
|
||||||
|
|
||||||
|
private $lastInsertIds = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeSuite
|
||||||
|
*/
|
||||||
|
public static function addFilesToSkeleton() {
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @When :user requests the deck list
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @When Sending a :method to :url with JSON
|
||||||
|
*/
|
||||||
|
public function sendingAToWithJSON($method, $url, \Behat\Gherkin\Node\PyStringNode $data) {
|
||||||
|
$baseUrl = substr($this->baseUrl, 0, -5);
|
||||||
|
|
||||||
|
$client = new Client;
|
||||||
|
$request = $client->createRequest(
|
||||||
|
$method,
|
||||||
|
$baseUrl . $url,
|
||||||
|
[
|
||||||
|
'cookies' => $this->cookieJar,
|
||||||
|
'json' => json_decode($data)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$request->addHeader('requesttoken', $this->requestToken);
|
||||||
|
try {
|
||||||
|
$this->response = $client->send($request);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
$this->response = $e->getResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When :user creates a new deck with name :name
|
||||||
|
*/
|
||||||
|
public function createsANewDeckWithName($user, $content) {
|
||||||
|
$client = new GuzzleHttp\Client();
|
||||||
|
$this->response = $client->post(
|
||||||
|
'http://localhost:8080/index.php/apps/deck/boards',
|
||||||
|
[
|
||||||
|
'form_params' => [
|
||||||
|
'name' => $name,
|
||||||
|
],
|
||||||
|
'auth' => [
|
||||||
|
$this->mappedUserId,
|
||||||
|
'test',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then the response should have a status code :code
|
||||||
|
* @param string $code
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function theResponseShouldHaveAStatusCode($code) {
|
||||||
|
$currentCode = $this->response->getStatusCode();
|
||||||
|
if ($currentCode !== (int)$code) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
sprintf(
|
||||||
|
'Expected %s as code got %s',
|
||||||
|
$code,
|
||||||
|
$currentCode
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then the response should be a JSON array with the following mandatory values
|
||||||
|
* @param TableNode $table
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table) {
|
||||||
|
$expectedValues = $table->getColumnsHash();
|
||||||
|
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
foreach ($expectedValues as $value) {
|
||||||
|
if ((string)$realResponseArray[$value['key']] !== (string)$value['value']) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
sprintf(
|
||||||
|
'Expected %s for key %s got %s',
|
||||||
|
(string)$value['value'],
|
||||||
|
$value['key'],
|
||||||
|
(string)$realResponseArray[$value['key']]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then the response should be a JSON array with a length of :length
|
||||||
|
* @param int $length
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function theResponseShouldBeAJsonArrayWithALengthOf($length) {
|
||||||
|
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
|
||||||
|
PHPUnit_Framework_Assert::assertEquals($realResponseArray, "foo");
|
||||||
|
if((int)count($realResponseArray) !== (int)$length) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
sprintf(
|
||||||
|
'Expected %d as length got %d',
|
||||||
|
$length,
|
||||||
|
count($realResponseArray)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then /^I should get:$/
|
||||||
|
*
|
||||||
|
* @param PyStringNode $string
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function iShouldGet(PyStringNode $string)
|
||||||
|
{
|
||||||
|
if ((string) $string !== trim($this->cliOutput)) {
|
||||||
|
throw new Exception(sprintf(
|
||||||
|
'Expected "%s" but received "%s".',
|
||||||
|
$string,
|
||||||
|
$this->cliOutput
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendJSONrequest($method, $url, $data) {
|
||||||
|
$baseUrl = substr($this->baseUrl, 0, -5);
|
||||||
|
|
||||||
|
$client = new Client;
|
||||||
|
$request = $client->createRequest(
|
||||||
|
$method,
|
||||||
|
$baseUrl . $url,
|
||||||
|
[
|
||||||
|
'cookies' => $this->cookieJar,
|
||||||
|
'json' => $data
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$request->addHeader('requesttoken', $this->requestToken);
|
||||||
|
try {
|
||||||
|
$this->response = $client->send($request);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
$this->response = $e->getResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^creates a board named "([^"]*)" with color "([^"]*)"$/
|
||||||
|
*/
|
||||||
|
public function createsABoardNamedWithColor($title, $color) {
|
||||||
|
|
||||||
|
$this->sendJSONrequest('POST', '/index.php/apps/deck/boards', [
|
||||||
|
'title' => $title,
|
||||||
|
'color' => $color
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$response = json_decode($this->response->getBody()->getContents(), true);
|
||||||
|
$this->lastInsertIds[$title] = $response['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When /^"([^"]*)" fetches the board named "([^"]*)"$/
|
||||||
|
*/
|
||||||
|
public function fetchesTheBoardNamed($user, $boardName) {
|
||||||
|
$this->loggingInUsingWebAs($user);
|
||||||
|
$id = $this->lastInsertIds[$boardName];
|
||||||
|
$this->sendJSONrequest('GET', '/index.php/apps/deck/boards/'.$id, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
37
tests/integration/features/decks.feature
Normal file
37
tests/integration/features/decks.feature
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
Feature: decks
|
||||||
|
|
||||||
|
Background:
|
||||||
|
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 Sending a "GET" to "/index.php/apps/deck/boards" with requesttoken
|
||||||
|
Then the HTTP status code should be "200"
|
||||||
|
And the 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 Sending a "GET" to "/index.php/apps/deck/boards/13379" with requesttoken
|
||||||
|
Then the HTTP status code should be "403"
|
||||||
|
And the Content-Type should be "application/json; charset=utf-8"
|
||||||
|
|
||||||
|
Scenario: Create a new board
|
||||||
|
Given Logging in using web as "admin"
|
||||||
|
When Sending a "POST" to "/index.php/apps/deck/boards" with JSON
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"title": "MyBoard",
|
||||||
|
"color": "000000"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
Then the HTTP status code should be "200"
|
||||||
|
And the Content-Type should be "application/json; charset=utf-8"
|
||||||
|
And the response should be a JSON array with the following mandatory values
|
||||||
|
|key|value|
|
||||||
|
|title|MyBoard|
|
||||||
|
|color|000000|
|
||||||
45
tests/integration/run.sh
Executable file
45
tests/integration/run.sh
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
OC_PATH=../../../../
|
||||||
|
OCC=${OC_PATH}occ
|
||||||
|
SCENARIO_TO_RUN=$1
|
||||||
|
HIDE_OC_LOGS=$2
|
||||||
|
|
||||||
|
# Nextcloud integration tests composer
|
||||||
|
(
|
||||||
|
cd ${OC_PATH}build/integration
|
||||||
|
composer install
|
||||||
|
)
|
||||||
|
INSTALLED=$($OCC status | grep installed: | cut -d " " -f 5)
|
||||||
|
|
||||||
|
if [ "$INSTALLED" == "true" ]; then
|
||||||
|
$OCC app:enable deck
|
||||||
|
else
|
||||||
|
if [ "$SCENARIO_TO_RUN" != "setup_features/setup.feature" ]; then
|
||||||
|
echo "Nextcloud instance needs to be installed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
composer install
|
||||||
|
composer dump-autoload
|
||||||
|
|
||||||
|
# avoid port collision on jenkins - use $EXECUTOR_NUMBER
|
||||||
|
if [ -z "$EXECUTOR_NUMBER" ]; then
|
||||||
|
EXECUTOR_NUMBER=0
|
||||||
|
fi
|
||||||
|
PORT=$((8080 + $EXECUTOR_NUMBER))
|
||||||
|
echo $PORT
|
||||||
|
php -S localhost:$PORT -t $OC_PATH &
|
||||||
|
PHPPID=$!
|
||||||
|
echo $PHPPID
|
||||||
|
|
||||||
|
export TEST_SERVER_URL="http://localhost:$PORT/ocs/"
|
||||||
|
|
||||||
|
vendor/bin/behat
|
||||||
|
RESULT=$?
|
||||||
|
|
||||||
|
kill $PHPPID
|
||||||
|
|
||||||
|
echo "runsh: Exit code: $RESULT"
|
||||||
|
exit $RESULT
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<phpunit bootstrap="bootstrap.php" colors="true">
|
<phpunit bootstrap="bootstrap.php" colors="true">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="integration">
|
<testsuite name="integration-database">
|
||||||
<directory>./integration</directory>
|
<directory>./integration/database</directory>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="integration-app">
|
||||||
|
<directory>./integration/app</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<filter>
|
<filter>
|
||||||
|
|||||||
Reference in New Issue
Block a user