diff --git a/.travis.yml b/.travis.yml
index 5fc443e9..038ebe90 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,6 +25,7 @@ before_script:
- sh -e /etc/init.d/xvfb start
- cd public
- php server.php --warmup
+ - php server.php --deploy
- (php -S 127.0.0.1:8008 server.php) &
- (node fusio/node_modules/protractor/bin/webdriver-manager start > /dev/null 2>&1) &
- sleep 8
@@ -33,6 +34,7 @@ script:
- cd public/fusio/tests
- node ../node_modules/protractor/bin/protractor conf.js
- cd ../../..
+ - vendor/bin/phpunit
env:
- DB=mysql
- DB=sqlite
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 00000000..88998354
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ ./tests
+
+
+
+
+ ./src
+
+
+
diff --git a/public/server.php b/public/server.php
index 15698ffb..2d5c7e40 100644
--- a/public/server.php
+++ b/public/server.php
@@ -20,66 +20,43 @@
*/
// entry point for the internal php server for testing
-$fileUris = [
- '^\/developer\/',
- '^\/documentation\/',
- '^\/fusio\/',
-];
+if (isset($_SERVER['REQUEST_URI'])) {
+ $fileUris = [
+ '^\/developer\/',
+ '^\/documentation\/',
+ '^\/fusio\/',
+ ];
-foreach ($fileUris as $regexp) {
- if (isset($_SERVER['REQUEST_URI']) && preg_match('/' . $regexp . '/', $_SERVER['REQUEST_URI'])) {
- return false;
+ foreach ($fileUris as $regexp) {
+ if (preg_match('/' . $regexp . '/', $_SERVER['REQUEST_URI'])) {
+ return false;
+ }
}
-}
-// strip if the requests starts with /index.php/
-if (isset($_SERVER['REQUEST_URI']) && substr($_SERVER['REQUEST_URI'], 0, 11) == '/index.php/') {
- $_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'], 10);
+ // strip if the requests starts with /index.php/
+ if (substr($_SERVER['REQUEST_URI'], 0, 11) == '/index.php/') {
+ $_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'], 10);
+ }
}
$loader = require(__DIR__ . '/../vendor/autoload.php');
-$container = require_once(__DIR__ . '/../container.php');
+$container = require_once(__DIR__ . '/../tests/container.php');
PSX\Framework\Bootstrap::setupEnvironment($container->get('config'));
-// setup connection
-$params = null;
-switch (getenv('DB')) {
- case 'mysql':
- $params = array(
- 'dbname' => 'fusio_ui',
- 'user' => 'root',
- 'password' => '',
- 'host' => 'localhost',
- 'driver' => 'pdo_mysql',
- );
- break;
-
- default:
- case 'sqlite':
- $params = array(
- 'path' => PSX_PATH_CACHE . '/fusio_ui.db',
- 'driver' => 'pdo_sqlite',
- );
- break;
-}
-
-$config = new Doctrine\DBAL\Configuration();
-$connection = Doctrine\DBAL\DriverManager::getConnection($params, $config);
-
-$container->set('connection', $connection);
-
if (isset($_SERVER['argv']) && in_array('--warmup', $_SERVER['argv'])) {
// warmup
$loader->addClassMap([
- 'Fusio\Impl\Tests\Fixture' => __DIR__ . '/../vendor/fusio/impl/tests/Fixture.php',
+ 'Fusio\Impl\Tests\Fixture' => __DIR__ . '/../vendor/fusio/impl/tests/Fixture.php',
'Fusio\Impl\Tests\TestSchema' => __DIR__ . '/../vendor/fusio/impl/tests/TestSchema.php',
]);
// create schema
+ /** @var \Doctrine\DBAL\Connection $connection */
+ $connection = $container->get('connection');
$fromSchema = $connection->getSchemaManager()->createSchema();
- $version = \Fusio\Impl\Database\Installer::getLatestVersion();
- $toSchema = $version->getSchema();
+ $version = \Fusio\Impl\Database\Installer::getLatestVersion();
+ $toSchema = $version->getSchema();
Fusio\Impl\Tests\TestSchema::appendSchema($toSchema);
$queries = $fromSchema->getMigrateToSql($toSchema, $connection->getDatabasePlatform());
@@ -92,6 +69,16 @@
PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT()->execute($connection, Fusio\Impl\Tests\Fixture::getDataSet());
echo 'Warmup successful' . "\n";
+} elseif (isset($_SERVER['argv']) && in_array('--deploy', $_SERVER['argv'])) {
+ /** @var \Symfony\Component\Console\Application $console */
+ $console = $container->get('console');
+ /** @var \Symfony\Component\Console\Command\Command $command */
+ $command = $container->get('console')->find('system:deploy');
+
+ $input = new \Symfony\Component\Console\Input\ArrayInput(['file' => __DIR__ . '/../.fusio.yml']);
+ $output = new \Symfony\Component\Console\Output\ConsoleOutput();
+
+ $command->run($input, $output);
} else {
// run
$request = $container->get('request_factory')->createRequest();
diff --git a/tests/Api/IndexTest.php b/tests/Api/IndexTest.php
new file mode 100644
index 00000000..83ff22e8
--- /dev/null
+++ b/tests/Api/IndexTest.php
@@ -0,0 +1,106 @@
+
+ *
+ * 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
+ * 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 .
+ */
+
+namespace Fusio\Custom\Tests\Api\Todo;
+
+use Fusio\Custom\Tests\ApiTestCase;
+
+/**
+ * IndexTest
+ *
+ * @author Christoph Kappestein
+ * @license http://www.gnu.org/licenses/agpl-3.0
+ * @link http://fusio-project.org
+ */
+class IndexTest extends ApiTestCase
+{
+ public function testGet()
+ {
+ $response = $this->send('GET', '');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "message": "Congratulations the installation of Fusio was successful",
+ "links": [
+ {
+ "rel": "about",
+ "name": "http:\/\/fusio-project.org"
+ }
+ ]
+}
+JSON;
+
+ $this->assertEquals(200, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPost()
+ {
+ $response = $this->send('POST', '');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Given request method is not supported"
+}
+JSON;
+
+ $this->assertEquals(405, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPut()
+ {
+ $response = $this->send('PUT', '');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Given request method is not supported"
+}
+JSON;
+
+ $this->assertEquals(405, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testDelete()
+ {
+ $response = $this->send('DELETE', '');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Given request method is not supported"
+}
+JSON;
+
+ $this->assertEquals(405, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+}
diff --git a/tests/Api/Todo/CollectionTest.php b/tests/Api/Todo/CollectionTest.php
new file mode 100644
index 00000000..6f860ebe
--- /dev/null
+++ b/tests/Api/Todo/CollectionTest.php
@@ -0,0 +1,265 @@
+
+ *
+ * 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
+ * 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 .
+ */
+
+namespace Fusio\Custom\Tests\Api\Todo;
+
+use Fusio\Custom\Tests\ApiTestCase;
+use PSX\Framework\Test\Environment;
+
+/**
+ * CollectionTest
+ *
+ * @author Christoph Kappestein
+ * @license http://www.gnu.org/licenses/agpl-3.0
+ * @link http://fusio-project.org
+ */
+class CollectionTest extends ApiTestCase
+{
+ public function testDocumentation()
+ {
+ $response = $this->send('GET', 'doc/*/todo');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "path": "\/todo",
+ "version": "*",
+ "status": 4,
+ "description": "",
+ "schema": {
+ "$schema": "http:\/\/json-schema.org\/draft-04\/schema#",
+ "id": "urn:schema.phpsx.org#",
+ "definitions": {
+ "Todo": {
+ "type": "object",
+ "title": "todo",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "status": {
+ "type": "integer"
+ },
+ "title": {
+ "type": "string"
+ },
+ "insertDate": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ "required": [
+ "title"
+ ]
+ },
+ "Collection": {
+ "type": "object",
+ "title": "collection",
+ "properties": {
+ "totalCount": {
+ "type": "integer"
+ },
+ "entry": {
+ "type": "array",
+ "items": {
+ "$ref": "#\/definitions\/Todo"
+ }
+ }
+ }
+ },
+ "Message": {
+ "type": "object",
+ "title": "message",
+ "properties": {
+ "success": {
+ "type": "boolean"
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ },
+ "GET-200-response": {
+ "$ref": "#\/definitions\/Collection"
+ },
+ "POST-request": {
+ "$ref": "#\/definitions\/Todo"
+ },
+ "POST-200-response": {
+ "$ref": "#\/definitions\/Message"
+ }
+ }
+ },
+ "methods": {
+ "GET": {
+ "responses": {
+ "200": "#\/definitions\/GET-200-response"
+ }
+ },
+ "POST": {
+ "request": "#\/definitions\/POST-request",
+ "responses": {
+ "200": "#\/definitions\/POST-200-response"
+ }
+ }
+ },
+ "links": [
+ {
+ "rel": "swagger",
+ "href": "\/index.php\/export\/swagger\/*\/todo"
+ },
+ {
+ "rel": "raml",
+ "href": "\/index.php\/export\/raml\/*\/todo"
+ }
+ ]
+}
+JSON;
+
+ $this->assertEquals(200, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testGet()
+ {
+ $response = $this->send('GET', 'todo');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "totalResults": "4",
+ "entry": [
+ {
+ "id": "3",
+ "status": "1",
+ "title": "Task 3",
+ "insertDate": "2016-02-17 20:15:56"
+ },
+ {
+ "id": "4",
+ "status": "1",
+ "title": "Task 4",
+ "insertDate": "2016-02-17 20:15:56"
+ },
+ {
+ "id": "2",
+ "status": "1",
+ "title": "Task 2",
+ "insertDate": "2016-02-17 20:14:49"
+ },
+ {
+ "id": "1",
+ "status": "1",
+ "title": "Task 1",
+ "insertDate": "2016-02-17 20:14:45"
+ }
+ ]
+}
+JSON;
+
+ $this->assertEquals(200, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPost()
+ {
+ $response = $this->sendAuthorized('POST', 'todo', ['title' => 'foo']);
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": true,
+ "message": "Insert successful"
+}
+JSON;
+
+ $this->assertEquals(200, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPostInvalidPayload()
+ {
+ $response = $this->sendAuthorized('POST', 'todo', ['foo' => 'bar']);
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "\/ the following properties are required: title"
+}
+JSON;
+
+ $this->assertEquals(500, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPostWithoutAuthorization()
+ {
+ $response = $this->send('POST', 'todo');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Missing authorization header"
+}
+JSON;
+
+ $this->assertEquals(401, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPut()
+ {
+ $response = $this->send('PUT', 'todo');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Given request method is not supported"
+}
+JSON;
+
+ $this->assertEquals(405, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testDelete()
+ {
+ $response = $this->send('DELETE', 'todo');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Given request method is not supported"
+}
+JSON;
+
+ $this->assertEquals(405, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+}
diff --git a/tests/Api/Todo/EntityTest.php b/tests/Api/Todo/EntityTest.php
new file mode 100644
index 00000000..a974c443
--- /dev/null
+++ b/tests/Api/Todo/EntityTest.php
@@ -0,0 +1,214 @@
+
+ *
+ * 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
+ * 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 .
+ */
+
+namespace Fusio\Custom\Tests\Api\Todo;
+
+use Fusio\Custom\Tests\ApiTestCase;
+
+/**
+ * EntityTest
+ *
+ * @author Christoph Kappestein
+ * @license http://www.gnu.org/licenses/agpl-3.0
+ * @link http://fusio-project.org
+ */
+class EntityTest extends ApiTestCase
+{
+ public function testDocumentation()
+ {
+ $response = $this->send('GET', 'doc/*/todo/4');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "path": "\/todo\/:todo_id",
+ "version": "*",
+ "status": 4,
+ "description": "",
+ "schema": {
+ "$schema": "http:\/\/json-schema.org\/draft-04\/schema#",
+ "id": "urn:schema.phpsx.org#",
+ "definitions": {
+ "Todo": {
+ "type": "object",
+ "title": "todo",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "status": {
+ "type": "integer"
+ },
+ "title": {
+ "type": "string"
+ },
+ "insertDate": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ "required": [
+ "title"
+ ]
+ },
+ "Passthru": {
+ "type": "object",
+ "title": "passthru",
+ "description": "No schema was specified all data will pass through. Please contact the API provider for more informations about the data format."
+ },
+ "Message": {
+ "type": "object",
+ "title": "message",
+ "properties": {
+ "success": {
+ "type": "boolean"
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ },
+ "GET-200-response": {
+ "$ref": "#\/definitions\/Todo"
+ },
+ "DELETE-request": {
+ "$ref": "#\/definitions\/Passthru"
+ },
+ "DELETE-200-response": {
+ "$ref": "#\/definitions\/Message"
+ }
+ }
+ },
+ "methods": {
+ "GET": {
+ "responses": {
+ "200": "#\/definitions\/GET-200-response"
+ }
+ },
+ "DELETE": {
+ "request": "#\/definitions\/DELETE-request",
+ "responses": {
+ "200": "#\/definitions\/DELETE-200-response"
+ }
+ }
+ },
+ "links": [
+ {
+ "rel": "swagger",
+ "href": "\/index.php\/export\/swagger\/*\/todo\/:todo_id"
+ },
+ {
+ "rel": "raml",
+ "href": "\/index.php\/export\/raml\/*\/todo\/:todo_id"
+ }
+ ]
+}
+JSON;
+
+ $this->assertEquals(200, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testGet()
+ {
+ $response = $this->send('GET', 'todo/4');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "id": "4",
+ "status": "1",
+ "title": "Task 4",
+ "insertDate": "2016-02-17 20:15:56"
+}
+JSON;
+
+ $this->assertEquals(200, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPost()
+ {
+ $response = $this->send('POST', 'todo/4');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Given request method is not supported"
+}
+JSON;
+
+ $this->assertEquals(405, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testPut()
+ {
+ $response = $this->send('PUT', 'todo/4');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Given request method is not supported"
+}
+JSON;
+
+ $this->assertEquals(405, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testDelete()
+ {
+ $response = $this->sendAuthorized('DELETE', 'todo/4');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": true,
+ "message": "Delete successful"
+}
+JSON;
+
+ $this->assertEquals(200, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+
+ public function testDeleteWithoutAuthorization()
+ {
+ $response = $this->send('DELETE', 'todo/4');
+
+ $actual = (string) $response->getBody();
+ $expect = <<<'JSON'
+{
+ "success": false,
+ "title": "Internal Server Error",
+ "message": "Missing authorization header"
+}
+JSON;
+
+ $this->assertEquals(401, $response->getStatusCode(), $actual);
+ $this->assertJsonStringEqualsJsonString($expect, $actual, $actual);
+ }
+}
diff --git a/tests/ApiTestCase.php b/tests/ApiTestCase.php
new file mode 100644
index 00000000..fb407e95
--- /dev/null
+++ b/tests/ApiTestCase.php
@@ -0,0 +1,221 @@
+
+ *
+ * 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
+ * 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 .
+ */
+
+namespace Fusio\Custom\Tests;
+
+use PSX\Json\Parser;
+
+/**
+ * ApiTestCase
+ *
+ * @author Christoph Kappestein
+ * @license http://www.gnu.org/licenses/agpl-3.0
+ * @link http://fusio-project.org
+ */
+abstract class ApiTestCase extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var \GuzzleHttp\Client
+ */
+ private static $client;
+
+ /**
+ * @var string
+ */
+ private static $accessToken;
+
+ protected function setUp()
+ {
+ }
+
+ protected function tearDown()
+ {
+ }
+
+ /**
+ * @param string $method
+ * @param string $uri
+ * @param string $body
+ * @param array $headers
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ protected function send($method, $uri, $body = null, array $headers = [])
+ {
+ $options = [
+ 'http_errors' => false,
+ ];
+
+ if (!empty($headers)) {
+ $options['headers'] = $headers;
+ }
+
+ if ($body !== null) {
+ if (is_array($body) || $body instanceof \stdClass) {
+ $options['json'] = $body;
+ } else {
+ $options['body'] = $body;
+ }
+ }
+
+ return self::getClient()->request($method, $uri, $options);
+ }
+
+ /**
+ * @param string $method
+ * @param string $uri
+ * @param string $body
+ * @param array $headers
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ protected function sendAuthorized($method, $uri, $body = null, array $headers = [])
+ {
+ $headers['Authorization'] = 'Bearer ' . self::getAccessToken();
+
+ return $this->send($method, $uri, $body, $headers);
+ }
+
+ /**
+ * @return string
+ */
+ private function getAccessToken()
+ {
+ if (self::$accessToken) {
+ return self::$accessToken;
+ }
+
+ $response = $this->send('POST', 'consumer/login', [
+ 'username' => 'Developer',
+ 'password' => 'qf2vX10Ec3wFZHx0K1eL',
+ ]);
+
+ $body = (string) $response->getBody();
+ $data = Parser::decode($body);
+
+ if (isset($data->token)) {
+ return self::$accessToken = $data->token;
+ } else {
+ $this->fail('Could not request access token');
+ }
+ }
+
+ private function getBaseUri()
+ {
+ $config = require __DIR__ . '/configuration.php';
+
+ if (is_array($config) && isset($config['psx_url'])) {
+ return $config['psx_url'];
+ } else {
+ $this->markTestSkipped('Could not determine base uri');
+ }
+ }
+
+ /**
+ * @return \GuzzleHttp\Client
+ */
+ private function getClient()
+ {
+ if (self::$client) {
+ return self::$client;
+ }
+
+ $client = new \GuzzleHttp\Client([
+ 'base_uri' => $this->getBaseUri(),
+ ]);
+
+ // check whether the base uri is available
+ $client->get('/');
+
+ // assign the client
+ self::$client = $client;
+
+ // create a scope which contains the routes from the deployment so that
+ // we can access the protected endpoints
+ $this->assignScopeToDeveloper();
+
+ return $client;
+ }
+
+ private function assignScopeToDeveloper()
+ {
+ // get backend access token
+ $response = $this->send('POST', 'backend/token', 'grant_type=client_credentials', [
+ 'Authorization' => 'Basic ' . base64_encode('Developer:qf2vX10Ec3wFZHx0K1eL')
+ ]);
+
+ $body = (string) $response->getBody();
+ $data = Parser::decode($body);
+ $token = isset($data->access_token) ? $data->access_token : null;
+
+ // check whether scope is already available
+ $response = $this->send('GET', 'backend/scope?search=todo', null, [
+ 'Authorization' => 'Bearer ' . $token
+ ]);
+
+ $body = (string) $response->getBody();
+ $data = Parser::decode($body);
+
+ if ($data->totalResults !== 1) {
+ // create scope
+ $data = [
+ 'name' => 'todo',
+ 'description' => 'Todo scope',
+ 'routes' => [[
+ 'routeId' => 68,
+ 'allow' => true,
+ 'methods' => 'GET|POST|PUT|DELETE',
+ ], [
+ 'routeId' => 67,
+ 'allow' => true,
+ 'methods' => 'GET|POST|PUT|DELETE',
+ ]],
+ ];
+
+ $response = $this->send('POST', 'backend/scope', Parser::encode($data), [
+ 'Authorization' => 'Bearer ' . $token
+ ]);
+
+ $body = (string) $response->getBody();
+ $data = Parser::decode($body);
+
+ if ($data->success !== true) {
+ $this->fail('Could not create scope');
+ }
+ }
+
+ // assign scope to user developer
+ $data = [
+ 'name' => 'Developer',
+ 'email' => 'developer@localhost.com',
+ 'scopes' => ['todo', 'authorization', 'consumer', 'backend'],
+ ];
+
+ $response = $this->send('PUT', 'backend/user/4', Parser::encode($data), [
+ 'Authorization' => 'Bearer ' . $token
+ ]);
+
+ $body = (string) $response->getBody();
+ $data = Parser::decode($body);
+
+ if ($data->success !== true) {
+ $this->fail('Could not assign scope');
+ }
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 00000000..6e01a036
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,5 @@
+ 'fusio_ui',
+ 'user' => 'root',
+ 'password' => '',
+ 'host' => 'localhost',
+ 'driver' => 'pdo_mysql',
+ );
+ break;
+
+ default:
+ case 'sqlite':
+ $connection = array(
+ 'path' => __DIR__ . '/../cache/fusio_ui.db',
+ 'driver' => 'pdo_sqlite',
+ );
+ break;
+}
+
+return array(
+
+ // Whether the implicit flow is allowed. This is mostly needed for
+ // javascript apps
+ 'fusio_grant_implicit' => true,
+
+ // Expire times of the different tokens which can be issued
+ 'fusio_expire_implicit' => 'PT1H',
+ 'fusio_expire_app' => 'P2D',
+ 'fusio_expire_backend' => 'PT1H',
+ 'fusio_expire_consumer' => 'PT1H',
+
+ // The secret key of a project. It is recommended to change this to another
+ // random value. This is used i.e. to encrypt the connection credentials in
+ // the database. NOTE IF YOU CHANGE THE KEY FUSIO CAN NO LONGER READ ANY
+ // DATA WHICH WAS ENCRYPTED BEFORE. BECAUSE OF THAT IT IS RECOMMENDED TO
+ // CHANGE THE KEY ONLY BEFORE THE INSTALLATION
+ 'fusio_project_key' => '42eec18ffdbffc9fda6110dcc705d6ce',
+
+ // Settings of the internal mailer. By default we use the internal PHP mail
+ // function
+ /*
+ 'fusio_mailer' => [
+ 'transport' => 'smtp',
+ 'host' => 'email-smtp.us-east-1.amazonaws.com',
+ 'port' => 587,
+ 'username' => 'my-username',
+ 'password' => 'my-password',
+ 'encryption' => 'tls',
+ ],
+ */
+
+ // The url to the psx public folder (i.e. http://127.0.0.1/psx/public or
+ // http://localhost.com)
+ 'psx_url' => 'http://127.0.0.1:8008',
+
+ // The default timezone
+ 'psx_timezone' => 'UTC',
+
+ // Whether PSX runs in debug mode or not. If not error reporting is set to 0
+ // Also several caches are used if the debug mode is false
+ 'psx_debug' => false,
+
+ // Database parameters which are used for the doctrine DBAL connection
+ // http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html
+ 'psx_connection' => $connection,
+
+ // Folder locations
+ 'psx_path_cache' => __DIR__ . '/../cache',
+ 'psx_path_library' => __DIR__ . '/../src',
+
+ // Supported writers
+ 'psx_supported_writer' => [
+ \PSX\Data\Writer\Json::class,
+ \PSX\Data\Writer\Jsonp::class,
+ \PSX\Data\Writer\Jsonx::class,
+ ],
+
+ // Global middleware which are applied before and after every request. Must
+ // bei either a classname, closure or PSX\Dispatch\FilterInterface instance
+ //'psx_filter_pre' => [],
+ //'psx_filter_post' => [],
+
+ // A closure which returns a doctrine cache implementation. If null the
+ // filesystem cache is used
+ //'psx_cache_factory' => null,
+
+ // A closure which returns a monolog handler implementation. If null the
+ // system handler is used
+ //'psx_logger_factory' => null,
+
+ // Class name of the error controller
+ //'psx_error_controller' => null,
+
+ // If you only want to change the appearance of the error page you can
+ // specify a custom template
+ //'psx_error_template' => null,
+
+);
diff --git a/tests/container.php b/tests/container.php
new file mode 100644
index 00000000..a95dd412
--- /dev/null
+++ b/tests/container.php
@@ -0,0 +1,13 @@
+setParameter('config.file', __DIR__ . '/configuration.php');
+
+return $container;