new: initial api and integration tests.

pull/80/head
Luciano Righetti 2022-01-05 17:44:02 +01:00
parent 63af5d080f
commit a473a9d3fb
14 changed files with 278 additions and 138 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ vendor
webroot/theme/node_modules
.vscode
docker/run/
.phpunit.result.cache
config.json

View File

@ -15,17 +15,17 @@
<testsuite name="app">
<directory>tests/TestCase/</directory>
</testsuite>
<!-- Add plugin test suites here. -->
<testsuite name="controller">
<directory>./tests/TestCase/Controller</directory>
</testsuite>
<testsuite name="api">
<directory>./tests/TestCase/Api</directory>
</testsuite>
</testsuites>
<!-- Setup a listener for fixtures -->
<listeners>
<listener class="Cake\TestSuite\Fixture\FixtureInjector">
<arguments>
<object class="Cake\TestSuite\Fixture\FixtureManager"/>
</arguments>
</listener>
</listeners>
<extensions>
<extension class="\Cake\TestSuite\Fixture\PHPUnitExtension" />
</extensions>
<!-- Ignore vendor tests in code coverage reports -->
<filter>

View File

@ -163,8 +163,8 @@ class AuditLogsTable extends AppTable
if ($this->user !== null) {
return $this->user;
}
$this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT];
$this->user = ['id' => 0, /*'org_id' => 0, */'authkey_id' => 0, 'request_type' => self::REQUEST_TYPE_DEFAULT, 'name' => ''];
$isShell = (php_sapi_name() === 'cli');
if ($isShell) {

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
use Authentication\PasswordHasher\DefaultPasswordHasher;
class AuthKeysFixture extends TestFixture
{
public $connection = 'test';
public const ADMIN_API_KEY = '4cd687b314a3b9c4d83264e6195b9a3706ef4c2f';
public function init(): void
{
$hasher = new DefaultPasswordHasher();
$this->records = [
[
'id' => 1,
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5',
'authkey' => $hasher->hash(self::ADMIN_API_KEY),
'authkey_start' => '4cd6',
'authkey_end' => '4c2f',
'expiration' => 0,
'user_id' => 1,
'comment' => '',
'created' => time(),
'modified' => time()
]
];
parent::init();
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class IndividualsFixture extends TestFixture
{
public $connection = 'test';
public $records = [
[
'id' => 1,
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e1',
'email' => 'admin@admin.test',
'first_name' => 'admin',
'last_name' => 'admin',
'position' => 'admin',
'created' => '2022-01-04 10:00:00',
'modified' => '2022-01-04 10:00:00'
]
];
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class RolesFixture extends TestFixture
{
public $connection = 'test';
public $records = [
[
'id' => 1,
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e4',
'name' => 'admin',
'is_default' => true,
'perm_admin' => true,
'perm_sync' => true,
'perm_org_admin' => true
]
];
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
use Authentication\PasswordHasher\DefaultPasswordHasher;
class UsersFixture extends TestFixture
{
public $connection = 'test';
public const ADMIN_USER = 'admin';
public const ADMIN_PASSWORD = 'Password1234';
public function init(): void
{
$hasher = new DefaultPasswordHasher();
$this->records = [
[
'id' => 1,
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5',
'username' => self::ADMIN_USER,
'password' => $hasher->hash(self::ADMIN_PASSWORD),
'role_id' => 1,
'individual_id' => 1,
'disabled' => 0,
'organisation_id' => 1,
'created' => '2022-01-04 10:00:00',
'modified' => '2022-01-04 10:00:00'
]
];
parent::init();
}
}

48
tests/README.md Normal file
View File

@ -0,0 +1,48 @@
# Testing
Add a test database to your `config/app_local.php` config file and set `debug` mode to `true`.
```php
'debug' => true,
'Datasources' => [
'default' => [
...
],
/*
* The test connection is used during the test suite.
*/
'test' => [
'host' => 'localhost',
'username' => 'cerebrate',
'password' => 'cerebrate',
'database' => 'cerebrate_test',
],
],
```
## Runing the tests
```
$ composer install
$ vendor/bin/phpunit
PHPUnit 8.5.22 by Sebastian Bergmann and contributors.
..... 5 / 5 (100%)
Time: 11.61 seconds, Memory: 26.00 MB
OK (5 tests, 15 assertions)
```
Running a specific suite:
```
$ vendor/bin/phpunit --testsuite=api
```
Available suites:
* `app`: runs all test suites
* `api`: runs only api tests
* `controller`: runs only controller tests
* _to be continued ..._
By default the database is re-generated before running the test suite, to skip this step and speed up the test run use the `-d skip-migrations` option:
```
$ vendor/bin/phpunit -d skip-migrations
```

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Api;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\UsersFixture;
class UsersApiTest extends TestCase
{
use IntegrationTestTrait;
protected $fixtures = [
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys'
];
public function testViewMe(): void
{
// ugly hack, $_SERVER['HTTP_AUTHORIZATION'] is not set automatically in test environment
$_SERVER['HTTP_AUTHORIZATION'] = AuthKeysFixture::ADMIN_API_KEY;
$this->configRequest([
'headers' => [
// this does not work: https://book.cakephp.org/4/en/development/testing.html#testing-stateless-authentication-and-apis
// 'Authorization' => AuthKeysFixture::ADMIN_API_KEY,
'Accept' => 'application/json'
]
]);
$this->get('/users/view');
$this->assertResponseOk();
$this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::ADMIN_USER));
}
}

View File

@ -40,10 +40,14 @@ class ApplicationTest extends IntegrationTestCase
$app->bootstrap();
$plugins = $app->getPlugins();
$this->assertCount(3, $plugins);
$this->assertCount(7, $plugins);
$this->assertSame('Bake', $plugins->get('Bake')->getName());
$this->assertSame('DebugKit', $plugins->get('DebugKit')->getName());
$this->assertSame('Migrations', $plugins->get('Migrations')->getName());
$this->assertSame('Authentication', $plugins->get('Authentication')->getName());
$this->assertSame('ADmad/SocialAuth', $plugins->get('ADmad/SocialAuth')->getName());
$this->assertSame('Tags', $plugins->get('Tags')->getName());
$this->assertSame('Cake/TwigView', $plugins->get('Cake/TwigView')->getName());
}
/**

View File

@ -1,126 +0,0 @@
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Test\TestCase\Controller;
use Cake\Core\Configure;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
/**
* PagesControllerTest class
*
* @uses \App\Controller\PagesController
*/
class PagesControllerTest extends TestCase
{
use IntegrationTestTrait;
/**
* testMultipleGet method
*
* @return void
*/
public function testMultipleGet()
{
$this->get('/');
$this->assertResponseOk();
$this->get('/');
$this->assertResponseOk();
}
/**
* testDisplay method
*
* @return void
*/
public function testDisplay()
{
$this->get('/pages/home');
$this->assertResponseOk();
$this->assertResponseContains('CakePHP');
$this->assertResponseContains('<html>');
}
/**
* Test that missing template renders 404 page in production
*
* @return void
*/
public function testMissingTemplate()
{
Configure::write('debug', false);
$this->get('/pages/not_existing');
$this->assertResponseError();
$this->assertResponseContains('Error');
}
/**
* Test that missing template in debug mode renders missing_template error page
*
* @return void
*/
public function testMissingTemplateInDebug()
{
Configure::write('debug', true);
$this->get('/pages/not_existing');
$this->assertResponseFailure();
$this->assertResponseContains('Missing Template');
$this->assertResponseContains('Stacktrace');
$this->assertResponseContains('not_existing.php');
}
/**
* Test directory traversal protection
*
* @return void
*/
public function testDirectoryTraversalProtection()
{
$this->get('/pages/../Layout/ajax');
$this->assertResponseCode(403);
$this->assertResponseContains('Forbidden');
}
/**
* Test that CSRF protection is applied to page rendering.
*
* @reutrn void
*/
public function testCsrfAppliedError()
{
$this->post('/pages/home', ['hello' => 'world']);
$this->assertResponseCode(403);
$this->assertResponseContains('CSRF');
}
/**
* Test that CSRF protection is applied to page rendering.
*
* @reutrn void
*/
public function testCsrfAppliedOk()
{
$this->enableCsrfToken();
$this->post('/pages/home', ['hello' => 'world']);
$this->assertResponseCode(200);
$this->assertResponseContains('CakePHP');
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Controller;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
use App\Test\Fixture\UsersFixture;
class UsersControllerTest extends TestCase
{
use IntegrationTestTrait;
protected $fixtures = [
'app.Individuals',
'app.Roles',
'app.Users'
];
public function testLogin(): void
{
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->post('/users/login', [
'username' => UsersFixture::ADMIN_USER,
'password' => UsersFixture::ADMIN_PASSWORD,
]);
$this->assertSessionHasKey('authUser.uuid');
}
}

View File

@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
@ -17,6 +18,8 @@ declare(strict_types=1);
use Cake\Core\Configure;
use Cake\Datasource\ConnectionManager;
use Cake\TestSuite\Fixture\SchemaLoader;
use Migrations\TestSuite\Migrator;
/**
* Test runner bootstrap.
@ -50,3 +53,17 @@ ConnectionManager::alias('test_debug_kit', 'debug_kit');
// does not allow the sessionid to be set after stdout
// has been written to.
session_id('cli');
// Load db schema from mysql.sql and run migrations
// super hacky way to skip migrations
if (!in_array('skip-migrations', $_SERVER['argv'])) {
// TODO: Removing mysql.sql and relying only in migrations would be ideal
// in the meantime, `'skip' => ['*']`, prevents migrations from droping already created tables
(new SchemaLoader())->loadSqlFiles('./INSTALL/mysql.sql', 'test');
$migrator = new Migrator();
$migrator->runMany([
['connection' => 'test', 'skip' => ['*']],
['plugin' => 'Tags', 'connection' => 'test', 'skip' => ['*']],
['plugin' => 'ADmad/SocialAuth', 'connection' => 'test', 'skip' => ['*']]
]);
}