From 96e9f8275f77180915cd4f3e7dbbad3a3a7546b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Wed, 30 Dec 2020 11:24:12 +0100 Subject: [PATCH] Run integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .github/workflows/integration.yml | 89 ++++ tests/integration/composer.json | 19 +- .../features/bootstrap/BasicStructure.php | 451 ------------------ .../features/bootstrap/FeatureContext.php | 39 +- 4 files changed, 108 insertions(+), 490 deletions(-) create mode 100644 .github/workflows/integration.yml delete mode 100644 tests/integration/features/bootstrap/BasicStructure.php diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 000000000..7a261a428 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,89 @@ +name: Integration tests + +on: + pull_request: + push: + branches: + - master + - stable* + +env: + APP_NAME: deck + +jobs: + integration: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-versions: ['7.4'] + databases: ['sqlite', 'mysql', 'pgsql'] + server-versions: ['master'] + + name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }} + + services: + postgres: + image: postgres + ports: + - 4445:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 + mysql: + image: mariadb + ports: + - 4444:3306/tcp + env: + MYSQL_ROOT_PASSWORD: rootpassword + options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5 + + steps: + - name: Checkout server + uses: actions/checkout@v2 + with: + repository: nextcloud/server + ref: ${{ matrix.server-versions }} + + - name: Checkout submodules + shell: bash + run: | + auth_header="$(git config --local --get http.https://github.com/.extraheader)" + git submodule sync --recursive + git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 + + - name: Checkout app + uses: actions/checkout@v2 + with: + path: apps/${{ env.APP_NAME }} + + - name: Set up php ${{ matrix.php-versions }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: phpunit + extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, + coverage: none + + - name: Set up PHPUnit + working-directory: apps/${{ env.APP_NAME }} + run: composer i + + - name: Set up Nextcloud + run: | + if [ "${{ matrix.databases }}" = "mysql" ]; then + export DB_PORT=4444 + elif [ "${{ matrix.databases }}" = "pgsql" ]; then + export DB_PORT=4445 + fi + mkdir data + ./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ app:enable --force ${{ env.APP_NAME }} + php -S localhost:8080 & + + - name: Run behat + working-directory: apps/${{ env.APP_NAME }}/tests/integration + run: ./run.sh diff --git a/tests/integration/composer.json b/tests/integration/composer.json index 6cd10678e..2f8d1b789 100644 --- a/tests/integration/composer.json +++ b/tests/integration/composer.json @@ -1,22 +1,17 @@ { "require-dev": { - "phpunit/phpunit": "~4.6", - "behat/behat": "^3.0", - "guzzlehttp/guzzle": "~5.0", + "phpunit/phpunit": "~6.5", + "behat/behat": "~3.8.0", + "guzzlehttp/guzzle": "6.5.2", "jarnaiz/behat-junit-formatter": "^1.3", - "sabre/dav": "3.2" + "sabre/dav": "3.2.3", + "symfony/event-dispatcher": "~4.4" }, "autoload": { - "files": [ - "../../../../build/integration/features/bootstrap/Auth.php", - "../../../../build/integration/features/bootstrap/Provisioning.php", - "../../../../build/integration/features/bootstrap/Sharing.php", - "../../../../build/integration/features/bootstrap/WebDav.php", - "../../../../build/integration/features/bootstrap/Trashbin.php" - ], "psr-0": { "": [ - "features/bootstrap/" + "features/bootstrap/", + "../../../../build/integration/features/bootstrap/" ] } } diff --git a/tests/integration/features/bootstrap/BasicStructure.php b/tests/integration/features/bootstrap/BasicStructure.php deleted file mode 100644 index 58b54efa8..000000000 --- a/tests/integration/features/bootstrap/BasicStructure.php +++ /dev/null @@ -1,451 +0,0 @@ - - * @author Joas Schilling - * @author Lukas Reschke - * @author Sergio Bertolin - * @author Thomas Müller - * - * @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 . - * - */ - -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); - } - } -} diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 3f42c2d0a..a6edbbe53 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -28,23 +28,7 @@ class FeatureContext implements Context { * @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(); - } + $this->sendJSONrequest($method, $url, json_decode($data)); } @@ -149,17 +133,18 @@ class FeatureContext implements Context { $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); + $this->response = $client->request( + $method, + $baseUrl . $url, + [ + 'cookies' => $this->cookieJar, + 'json' => $data, + 'headers' => [ + 'requesttoken' => $this->requestToken + ] + ] + ); } catch (ClientException $e) { $this->response = $e->getResponse(); }