Merge remote-tracking branch 'MyIgel/session'

This commit is contained in:
Igor Scheller 2018-09-30 19:31:14 +02:00
commit b46207f911
23 changed files with 944 additions and 60 deletions

View File

@ -132,6 +132,15 @@ return [
'4XL' => '4XL' '4XL' => '4XL'
], ],
// Session config
'session' => [
// Supported: pdo or native
'driver' => env('SESSION_DRIVER', 'pdo'),
// Cookie name
'name' => 'session',
],
// IP addresses of reverse proxies that are trusted, can be an array or a comma separated list // IP addresses of reverse proxies that are trusted, can be an array or a comma separated list
'trusted_proxies' => env('TRUSTED_PROXIES', ['127.0.0.0/8', '::ffff:127.0.0.0/8', '::1/128']), 'trusted_proxies' => env('TRUSTED_PROXIES', ['127.0.0.0/8', '::ffff:127.0.0.0/8', '::1/128']),
]; ];

View File

@ -17,12 +17,14 @@ class CreateLogEntriesTable extends Migration
$table->timestamp('created_at')->nullable(); $table->timestamp('created_at')->nullable();
}); });
$this->schema->getConnection()->unprepared(' if ($this->schema->hasTable('LogEntries')) {
INSERT INTO log_entries (`id`, `level`, `message`, `created_at`) $this->schema->getConnection()->unprepared('
SELECT `id`, `level`, `message`, FROM_UNIXTIME(`timestamp`) FROM LogEntries INSERT INTO log_entries (`id`, `level`, `message`, `created_at`)
'); SELECT `id`, `level`, `message`, FROM_UNIXTIME(`timestamp`) FROM LogEntries
');
$this->schema->dropIfExists('LogEntries'); $this->schema->drop('LogEntries');
}
} }
/** /**

View File

@ -0,0 +1,27 @@
<?php
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateSessionsTable extends Migration
{
/**
* Run the migration
*/
public function up()
{
$this->schema->create('sessions', function (Blueprint $table) {
$table->string('id')->unique();
$table->text('payload');
$table->dateTime('last_activity')->useCurrent();
});
}
/**
* Reverse the migration
*/
public function down()
{
$this->schema->dropIfExists('sessions');
}
}

98
src/Database/Database.php Normal file
View File

@ -0,0 +1,98 @@
<?php
namespace Engelsystem\Database;
use Illuminate\Database\Connection as DatabaseConnection;
use PDO;
class Database
{
/** @var DatabaseConnection */
protected $connection;
/**
* @param DatabaseConnection $connection
*/
public function __construct(DatabaseConnection $connection)
{
$this->connection = $connection;
}
/**
* Run a select query
*
* @param string $query
* @param array $bindings
* @return object[]
*/
public function select($query, array $bindings = [])
{
return $this->connection->select($query, $bindings);
}
/**
* Run a select query and return only the first result or null if no result is found.
*
* @param string $query
* @param array $bindings
* @return object|null
*/
public function selectOne($query, array $bindings = [])
{
return $this->connection->selectOne($query, $bindings);
}
/**
* Run an insert query
*
* @param string $query
* @param array $bindings
* @return bool
*/
public function insert($query, array $bindings = [])
{
return $this->connection->insert($query, $bindings);
}
/**
* Run an update query
*
* @param string $query
* @param array $bindings
* @return int
*/
public function update($query, array $bindings = [])
{
return $this->connection->update($query, $bindings);
}
/**
* Run a delete query
*
* @param string $query
* @param array $bindings
* @return int
*/
public function delete($query, array $bindings = [])
{
return $this->connection->delete($query, $bindings);
}
/**
* Get the PDO instance
*
* @return PDO
*/
public function getPdo()
{
return $this->connection->getPdo();
}
/**
* @return DatabaseConnection
*/
public function getConnection()
{
return $this->connection;
}
}

View File

@ -5,6 +5,7 @@ namespace Engelsystem\Database;
use Engelsystem\Container\ServiceProvider; use Engelsystem\Container\ServiceProvider;
use Exception; use Exception;
use Illuminate\Database\Capsule\Manager as CapsuleManager; use Illuminate\Database\Capsule\Manager as CapsuleManager;
use Illuminate\Database\Connection as DatabaseConnection;
use PDOException; use PDOException;
class DatabaseServiceProvider extends ServiceProvider class DatabaseServiceProvider extends ServiceProvider
@ -30,14 +31,25 @@ class DatabaseServiceProvider extends ServiceProvider
$capsule->bootEloquent(); $capsule->bootEloquent();
$capsule->getConnection()->useDefaultSchemaGrammar(); $capsule->getConnection()->useDefaultSchemaGrammar();
$pdo = null;
try { try {
$capsule->getConnection()->getPdo(); $pdo = $capsule->getConnection()->getPdo();
} catch (PDOException $e) { } catch (PDOException $e) {
$this->exitOnError(); $this->exitOnError();
} }
$this->app->instance('db', $capsule); $this->app->instance(CapsuleManager::class, $capsule);
$this->app->instance(Db::class, $capsule);
Db::setDbManager($capsule); Db::setDbManager($capsule);
$connection = $capsule->getConnection();
$this->app->instance(DatabaseConnection::class, $connection);
$database = $this->app->make(Database::class);
$this->app->instance(Database::class, $database);
$this->app->instance('db', $database);
$this->app->instance('db.pdo', $pdo);
$this->app->instance('db.connection', $connection);
} }
/** /**

View File

@ -6,6 +6,7 @@ use Illuminate\Database\Capsule\Manager as CapsuleManager;
use Illuminate\Database\Connection as DatabaseConnection; use Illuminate\Database\Connection as DatabaseConnection;
use PDO; use PDO;
/** @deprecated */
class Db class Db
{ {
/** @var CapsuleManager */ /** @var CapsuleManager */

View File

@ -18,7 +18,7 @@ class Migrate
protected $app; protected $app;
/** @var SchemaBuilder */ /** @var SchemaBuilder */
protected $scheme; protected $schema;
/** @var callable */ /** @var callable */
protected $output; protected $output;
@ -29,13 +29,13 @@ class Migrate
/** /**
* Migrate constructor * Migrate constructor
* *
* @param SchemaBuilder $scheme * @param SchemaBuilder $schema
* @param Application $app * @param Application $app
*/ */
public function __construct(SchemaBuilder $scheme, Application $app) public function __construct(SchemaBuilder $schema, Application $app)
{ {
$this->app = $app; $this->app = $app;
$this->scheme = $scheme; $this->schema = $schema;
$this->output = function () { }; $this->output = function () { };
} }
@ -76,6 +76,21 @@ class Migrate
} }
} }
/**
* Setup migration tables
*/
public function initMigration()
{
if ($this->schema->hasTable($this->table)) {
return;
}
$this->schema->create($this->table, function (Blueprint $table) {
$table->increments('id');
$table->string('migration');
});
}
/** /**
* Get all migrated migrations * Get all migrated migrations
* *
@ -155,21 +170,6 @@ class Migrate
return glob($dir . '/*_*.php'); return glob($dir . '/*_*.php');
} }
/**
* Setup migration tables
*/
protected function initMigration()
{
if ($this->scheme->hasTable($this->table)) {
return;
}
$this->scheme->create($this->table, function (Blueprint $table) {
$table->increments('id');
$table->string('migration');
});
}
/** /**
* Init a table query * Init a table query
* *
@ -177,7 +177,7 @@ class Migrate
*/ */
protected function getTableQuery() protected function getTableQuery()
{ {
return $this->scheme->getConnection()->table($this->table); return $this->schema->getConnection()->table($this->table);
} }
/** /**

View File

@ -3,16 +3,19 @@
namespace Engelsystem\Database\Migration; namespace Engelsystem\Database\Migration;
use Engelsystem\Container\ServiceProvider; use Engelsystem\Container\ServiceProvider;
use Engelsystem\Database\Db; use Engelsystem\Database\Database;
use Illuminate\Database\Schema\Builder as SchemaBuilder; use Illuminate\Database\Schema\Builder as SchemaBuilder;
class MigrationServiceProvider extends ServiceProvider class MigrationServiceProvider extends ServiceProvider
{ {
public function register() public function register()
{ {
$schema = Db::connection()->getSchemaBuilder(); /** @var Database $database */
$this->app->instance('db.scheme', $schema); $database = $this->app->get(Database::class);
$this->app->bind(SchemaBuilder::class, 'db.scheme'); $schema = $database->getConnection()->getSchemaBuilder();
$this->app->instance('db.schema', $schema);
$this->app->bind(SchemaBuilder::class, 'db.schema');
$migration = $this->app->make(Migrate::class); $migration = $this->app->make(Migrate::class);
$this->app->instance('db.migration', $migration); $this->app->instance('db.migration', $migration);

View File

@ -0,0 +1,75 @@
<?php
namespace Engelsystem\Http\SessionHandlers;
use SessionHandlerInterface;
abstract class AbstractHandler implements SessionHandlerInterface
{
/** @var string */
protected $name;
/** @var string */
protected $sessionPath;
/**
* Bootstrap the session handler
*
* @param string $sessionPath
* @param string $name
* @return bool
*/
public function open($sessionPath, $name): bool
{
$this->name = $name;
$this->sessionPath = $sessionPath;
return true;
}
/**
* Shutdown the session handler
*
* @return bool
*/
public function close(): bool
{
return true;
}
/**
* Remove old sessions
*
* @param int $maxLifetime
* @return bool
*/
public function gc($maxLifetime): bool
{
return true;
}
/**
* Read session data
*
* @param string $id
* @return string
*/
abstract public function read($id): string;
/**
* Write session data
*
* @param string $id
* @param string $data
* @return bool
*/
abstract public function write($id, $data): bool;
/**
* Delete a session
*
* @param string $id
* @return bool
*/
abstract public function destroy($id): bool;
}

View File

@ -0,0 +1,108 @@
<?php
namespace Engelsystem\Http\SessionHandlers;
use Engelsystem\Database\Database;
use Illuminate\Database\Query\Builder as QueryBuilder;
class DatabaseHandler extends AbstractHandler
{
/** @var Database */
protected $database;
/**
* @param Database $database
*/
public function __construct(Database $database)
{
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public function read($id): string
{
$session = $this->getQuery()
->where('id', '=', $id)
->first();
return $session ? $session->payload : '';
}
/**
* {@inheritdoc}
*/
public function write($id, $data): bool
{
$values = [
'payload' => $data,
'last_activity' => $this->getCurrentTimestamp(),
];
$session = $this->getQuery()
->where('id', '=', $id)
->first();
if (!$session) {
return $this->getQuery()
->insert($values + [
'id' => $id,
]);
}
$this->getQuery()
->where('id', '=', $id)
->update($values);
// The update return can't be used directly because it won't change if the second call is in the same second
return true;
}
/**
* {@inheritdoc}
*/
public function destroy($id): bool
{
$this->getQuery()
->where('id', '=', $id)
->delete();
return true;
}
/**
* {@inheritdoc}
*/
public function gc($maxLifetime): bool
{
$timestamp = $this->getCurrentTimestamp(-$maxLifetime);
$this->getQuery()
->where('last_activity', '<', $timestamp)
->delete();
return true;
}
/**
* @return QueryBuilder
*/
protected function getQuery(): QueryBuilder
{
return $this->database
->getConnection()
->table('sessions');
}
/**
* Format the SQL timestamp
*
* @param int $diff
* @return string
*/
protected function getCurrentTimestamp(int $diff = 0): string
{
return date('Y-m-d H:i:s', strtotime(sprintf('%+d seconds', $diff)));
}
}

View File

@ -2,7 +2,9 @@
namespace Engelsystem\Http; namespace Engelsystem\Http;
use Engelsystem\Config\Config;
use Engelsystem\Container\ServiceProvider; use Engelsystem\Container\ServiceProvider;
use Engelsystem\Http\SessionHandlers\DatabaseHandler;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
@ -38,7 +40,24 @@ class SessionServiceProvider extends ServiceProvider
return $this->app->make(MockArraySessionStorage::class); return $this->app->make(MockArraySessionStorage::class);
} }
return $this->app->make(NativeSessionStorage::class, ['options' => ['cookie_httponly' => true]]); /** @var Config $config */
$config = $this->app->get('config');
$sessionConfig = $config->get('session');
$handler = null;
switch ($sessionConfig['driver']) {
case 'pdo':
$handler = $this->app->make(DatabaseHandler::class);
break;
}
return $this->app->make(NativeSessionStorage::class, [
'options' => [
'cookie_httponly' => true,
'name' => $sessionConfig['name'],
],
'handler' => $handler,
]);
} }
/** /**

View File

@ -2,6 +2,8 @@
namespace Engelsystem\Models; namespace Engelsystem\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
abstract class BaseModel extends Model abstract class BaseModel extends Model
@ -10,6 +12,8 @@ abstract class BaseModel extends Model
public $timestamps = false; public $timestamps = false;
/** /**
* Create a new model
*
* @param array $attributes * @param array $attributes
* @return BaseModel * @return BaseModel
*/ */
@ -20,4 +24,16 @@ abstract class BaseModel extends Model
return $instance; return $instance;
} }
/**
* Find a model by its primary key
*
* @param mixed $id
* @param array $columns
* @return Builder|Builder[]|Collection|Model|null
*/
public static function find($id, $columns = ['*'])
{
return static::query()->find($id, $columns);
}
} }

View File

@ -2,12 +2,16 @@
namespace Engelsystem\Test\Unit\Database; namespace Engelsystem\Test\Unit\Database;
use Engelsystem\Application;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Database\Database;
use Engelsystem\Database\DatabaseServiceProvider; use Engelsystem\Database\DatabaseServiceProvider;
use Engelsystem\Database\Db;
use Engelsystem\Test\Unit\ServiceProviderTest; use Engelsystem\Test\Unit\ServiceProviderTest;
use Exception; use Exception;
use Illuminate\Database\Capsule\Manager as CapsuleManager; use Illuminate\Database\Capsule\Manager as CapsuleManager;
use Illuminate\Database\Connection; use Illuminate\Database\Connection;
use PDO;
use PDOException; use PDOException;
use PHPUnit_Framework_MockObject_MockObject as MockObject; use PHPUnit_Framework_MockObject_MockObject as MockObject;
@ -18,9 +22,29 @@ class DatabaseServiceProviderTest extends ServiceProviderTest
*/ */
public function testRegister() public function testRegister()
{ {
list($app, $dbManager) = $this->prepare(['driver' => 'sqlite', 'database' => ':memory:']); /** @var Application|MockObject $app */
/** @var CapsuleManager|MockObject $dbManager */
/** @var PDO|MockObject $pdo */
/** @var Database|MockObject $database */
/** @var Connection|MockObject $connection */
list($app, $dbManager, $pdo, $database, $connection) = $this->prepare(
[
'driver' => 'sqlite',
'database' => ':memory:'
]
);
$this->setExpects($app, 'instance', ['db', $dbManager]); $app->expects($this->exactly(7))
->method('instance')
->withConsecutive(
[CapsuleManager::class, $dbManager],
[Db::class, $dbManager],
[Connection::class, $connection],
[Database::class, $database],
['db', $database],
['db.pdo', $pdo],
['db.connection', $connection]
);
$serviceProvider = new DatabaseServiceProvider($app); $serviceProvider = new DatabaseServiceProvider($app);
$serviceProvider->register(); $serviceProvider->register();
@ -64,28 +88,46 @@ class DatabaseServiceProviderTest extends ServiceProviderTest
$connection = $this->getMockBuilder(Connection::class) $connection = $this->getMockBuilder(Connection::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
/** @var PDO|MockObject $pdo */
$pdo = $this->getMockBuilder(PDO::class)
->disableOriginalConstructor()
->getMock();
/** @var Database|MockObject $database */
$database = $this->getMockBuilder(Database::class)
->disableOriginalConstructor()
->getMock();
$app = $this->getApp(['get', 'make', 'instance']); $app = $this->getApp(['get', 'make', 'instance']);
$this->setExpects($app, 'get', ['config'], $config); $this->setExpects($app, 'get', ['config'], $config);
$this->setExpects($app, 'make', [CapsuleManager::class], $dbManager);
$this->setExpects($config, 'get', ['database'], $dbConfigData, $this->atLeastOnce()); $this->setExpects($config, 'get', ['database'], $dbConfigData, $this->atLeastOnce());
$app->expects($this->atLeastOnce())
->method('make')
->withConsecutive(
[CapsuleManager::class],
[Database::class]
)
->willReturn(
$dbManager,
$database
);
$this->setExpects($dbManager, 'setAsGlobal'); $this->setExpects($dbManager, 'setAsGlobal');
$this->setExpects($dbManager, 'bootEloquent'); $this->setExpects($dbManager, 'bootEloquent');
$this->setExpects($connection, 'useDefaultSchemaGrammar'); $this->setExpects($connection, 'useDefaultSchemaGrammar');
$connection->expects($this->once()) $connection->expects($this->once())
->method('getPdo') ->method('getPdo')
->willReturnCallback(function () use ($getPdoThrowException) { ->willReturnCallback(function () use ($getPdoThrowException, $pdo) {
if ($getPdoThrowException) { if ($getPdoThrowException) {
throw new PDOException(); throw new PDOException();
} }
return ''; return $pdo;
}); });
$this->setExpects($dbManager, 'getConnection', [], $connection, $this->atLeastOnce()); $this->setExpects($dbManager, 'getConnection', [], $connection, $this->atLeastOnce());
return [$app, $dbManager]; return [$app, $dbManager, $pdo, $database, $connection];
} }
} }

View File

@ -0,0 +1,146 @@
<?php
namespace Engelsystem\Test\Unit\Database;
use Engelsystem\Database\Database;
use Illuminate\Database\Capsule\Manager as CapsuleManager;
use Illuminate\Database\Connection as DatabaseConnection;
use PDO;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
class DatabaseTest extends TestCase
{
/** @var DatabaseConnection */
protected $connection;
/**
* @covers \Engelsystem\Database\Database::__construct()
* @covers \Engelsystem\Database\Database::getPdo()
* @covers \Engelsystem\Database\Database::getConnection()
*/
public function testInit()
{
/** @var Pdo|MockObject $pdo */
$pdo = $this->getMockBuilder(Pdo::class)
->disableOriginalConstructor()
->getMock();
/** @var DatabaseConnection|MockObject $databaseConnection */
$databaseConnection = $this->getMockBuilder(DatabaseConnection::class)
->disableOriginalConstructor()
->getMock();
$databaseConnection->expects($this->atLeastOnce())
->method('getPdo')
->willReturn($pdo);
$db = new Database($databaseConnection);
$this->assertEquals($databaseConnection, $db->getConnection());
$this->assertEquals($pdo, $db->getPdo());
$this->assertInstanceOf(PDO::class, $db->getPdo());
}
/**
* @covers \Engelsystem\Database\Database::select()
*/
public function testSelect()
{
$db = new Database($this->connection);
$return = $db->select('SELECT * FROM test_data');
$this->assertTrue(count($return) > 3);
$return = $db->select('SELECT * FROM test_data WHERE id = ?', [2]);
$this->assertCount(1, $return);
}
/**
* @covers \Engelsystem\Database\Database::selectOne()
*/
public function testSelectOne()
{
$db = new Database($this->connection);
$return = $db->selectOne('SELECT * FROM test_data');
$this->assertEquals('Foo', $return->data);
$return = $db->selectOne('SELECT * FROM test_data WHERE id = -1');
$this->assertEmpty($return);
$return = $db->selectOne('SELECT * FROM test_data WHERE id = ?', [3]);
$this->assertTrue(!is_array($return));
}
/**
* @covers \Engelsystem\Database\Database::insert()
*/
public function testInsert()
{
$db = new Database($this->connection);
$result = $db->insert("INSERT INTO test_data (id, data) VALUES (5, 'Some random text'), (6, 'another text')");
$this->assertTrue($result);
}
/**
* @covers \Engelsystem\Database\Database::update()
*/
public function testUpdate()
{
$db = new Database($this->connection);
$count = $db->update("UPDATE test_data SET data='NOPE' WHERE data LIKE '%Replaceme%'");
$this->assertEquals(3, $count);
$count = $db->update("UPDATE test_data SET data=? WHERE data LIKE '%NOPE%'", ['Some random text!']);
$this->assertEquals(3, $count);
}
/**
* @covers \Engelsystem\Database\Database::delete()
*/
public function testDelete()
{
$db = new Database($this->connection);
$count = $db->delete('DELETE FROM test_data WHERE id=1');
$this->assertEquals(1, $count);
$count = $db->delete('DELETE FROM test_data WHERE data LIKE ?', ['%Replaceme%']);
$this->assertEquals(3, $count);
}
/**
* Setup in memory database
*/
protected function setUp()
{
$dbManager = new CapsuleManager();
$dbManager->addConnection(['driver' => 'sqlite', 'database' => ':memory:']);
$connection = $dbManager->getConnection();
$this->connection = $connection;
$connection->getPdo()->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$connection->statement(
'
CREATE TABLE test_data(
id INT PRIMARY KEY NOT NULL,
data TEXT NOT NULL
);
');
$connection->statement('CREATE UNIQUE INDEX test_data_id_uindex ON test_data (id);');
$connection->insert("
INSERT INTO test_data (id, data)
VALUES
(1, 'Foo'),
(2, 'Bar'),
(3, 'Batz'),
(4, 'Lorem ipsum dolor sit'),
(10, 'Replaceme ipsum dolor sit amet'),
(11, 'Lorem Replaceme dolor sit amet'),
(12, 'Lorem ipsum Replaceme sit amet')
;");
}
}

View File

@ -120,12 +120,12 @@ class MigrateTest extends TestCase
$dbManager->bootEloquent(); $dbManager->bootEloquent();
$db = $dbManager->getConnection(); $db = $dbManager->getConnection();
$db->useDefaultSchemaGrammar(); $db->useDefaultSchemaGrammar();
$scheme = $db->getSchemaBuilder(); $schema = $db->getSchemaBuilder();
$app->instance('scheme', $scheme); $app->instance('schema', $schema);
$app->bind(SchemaBuilder::class, 'scheme'); $app->bind(SchemaBuilder::class, 'schema');
$migration = new Migrate($scheme, $app); $migration = new Migrate($schema, $app);
$messages = []; $messages = [];
$migration->setOutput(function ($msg) use (&$messages) { $migration->setOutput(function ($msg) use (&$messages) {
@ -134,7 +134,7 @@ class MigrateTest extends TestCase
$migration->run(__DIR__ . '/Stub', Migrate::UP); $migration->run(__DIR__ . '/Stub', Migrate::UP);
$this->assertTrue($scheme->hasTable('migrations')); $this->assertTrue($schema->hasTable('migrations'));
$migrations = $db->table('migrations')->get(); $migrations = $db->table('migrations')->get();
$this->assertCount(3, $migrations); $this->assertCount(3, $migrations);
@ -143,7 +143,7 @@ class MigrateTest extends TestCase
$this->assertTrue($migrations->contains('migration', '2017_12_24_053300_another_stuff')); $this->assertTrue($migrations->contains('migration', '2017_12_24_053300_another_stuff'));
$this->assertTrue($migrations->contains('migration', '2022_12_22_221222_add_some_feature')); $this->assertTrue($migrations->contains('migration', '2022_12_22_221222_add_some_feature'));
$this->assertTrue($scheme->hasTable('lorem_ipsum')); $this->assertTrue($schema->hasTable('lorem_ipsum'));
$migration->run(__DIR__ . '/Stub', Migrate::DOWN, true); $migration->run(__DIR__ . '/Stub', Migrate::DOWN, true);
@ -155,6 +155,6 @@ class MigrateTest extends TestCase
$migrations = $db->table('migrations')->get(); $migrations = $db->table('migrations')->get();
$this->assertCount(0, $migrations); $this->assertCount(0, $migrations);
$this->assertFalse($scheme->hasTable('lorem_ipsum')); $this->assertFalse($schema->hasTable('lorem_ipsum'));
} }
} }

View File

@ -2,11 +2,10 @@
namespace Engelsystem\Test\Unit\Database\Migration; namespace Engelsystem\Test\Unit\Database\Migration;
use Engelsystem\Database\Db; use Engelsystem\Database\Database;
use Engelsystem\Database\Migration\Migrate; use Engelsystem\Database\Migration\Migrate;
use Engelsystem\Database\Migration\MigrationServiceProvider; use Engelsystem\Database\Migration\MigrationServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest; use Engelsystem\Test\Unit\ServiceProviderTest;
use Illuminate\Database\Capsule\Manager as CapsuleManager;
use Illuminate\Database\Connection; use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Builder as SchemaBuilder; use Illuminate\Database\Schema\Builder as SchemaBuilder;
use PHPUnit_Framework_MockObject_MockObject as MockObject; use PHPUnit_Framework_MockObject_MockObject as MockObject;
@ -22,8 +21,8 @@ class MigrationServiceProviderTest extends ServiceProviderTest
$migration = $this->getMockBuilder(Migrate::class) $migration = $this->getMockBuilder(Migrate::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
/** @var MockObject|CapsuleManager $dbManager */ /** @var Database|MockObject $database */
$dbManager = $this->getMockBuilder(CapsuleManager::class) $database = $this->getMockBuilder(Database::class)
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
/** @var MockObject|Connection $dbConnection */ /** @var MockObject|Connection $dbConnection */
@ -35,19 +34,19 @@ class MigrationServiceProviderTest extends ServiceProviderTest
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$app = $this->getApp(['make', 'instance', 'bind']); $app = $this->getApp(['make', 'instance', 'bind', 'get']);
$app->expects($this->atLeastOnce()) $app->expects($this->atLeastOnce())
->method('instance') ->method('instance')
->withConsecutive(['db.scheme'], ['db.migration']) ->withConsecutive(['db.schema'], ['db.migration'])
->willReturnOnConsecutiveCalls($schemaBuilder, $migration); ->willReturnOnConsecutiveCalls($schemaBuilder, $migration);
$this->setExpects($app, 'bind', [SchemaBuilder::class, 'db.scheme']); $this->setExpects($app, 'bind', [SchemaBuilder::class, 'db.schema']);
$this->setExpects($app, 'make', [Migrate::class], $migration); $this->setExpects($app, 'make', [Migrate::class], $migration);
$this->setExpects($app, 'get', [Database::class], $database);
$this->setExpects($dbConnection, 'getSchemaBuilder', null, $schemaBuilder); $this->setExpects($dbConnection, 'getSchemaBuilder', null, $schemaBuilder);
$this->setExpects($dbManager, 'getConnection', null, $dbConnection); $this->setExpects($database, 'getConnection', null, $dbConnection);
Db::setDbManager($dbManager);
$serviceProvider = new MigrationServiceProvider($app); $serviceProvider = new MigrationServiceProvider($app);
$serviceProvider->register(); $serviceProvider->register();

View File

@ -0,0 +1,47 @@
<?php
namespace Engelsystem\Test\Unit;
use Engelsystem\Application;
use Engelsystem\Database\Database;
use Engelsystem\Database\Migration\Migrate;
use Engelsystem\Database\Migration\MigrationServiceProvider;
use Illuminate\Database\Capsule\Manager as CapsuleManager;
use PDO;
trait HasDatabase
{
/** @var Database */
protected $database;
/**
* Setup in memory database
*/
protected function initDatabase()
{
$dbManager = new CapsuleManager();
$dbManager->addConnection(['driver' => 'sqlite', 'database' => ':memory:']);
$connection = $dbManager->getConnection();
$connection->getPdo()->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->database = new Database($connection);
$app = new Application();
$app->instance(Database::class, $this->database);
$app->register(MigrationServiceProvider::class);
/** @var Migrate $migration */
$migration = $app->get('db.migration');
$migration->initMigration();
$this->database
->getConnection()
->table('migrations')
->insert([
['migration' => '2018_01_01_000001_import_install_sql'],
['migration' => '2018_01_01_000002_import_update_sql'],
]);
$migration->run(__DIR__ . '/../../db/migrations');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Engelsystem\Test\Unit\Http\SessionHandlers;
use Engelsystem\Test\Unit\Http\SessionHandlers\Stub\ArrayHandler;
use PHPUnit\Framework\TestCase;
class AbstractHandlerTest extends TestCase
{
/**
* @covers \Engelsystem\Http\SessionHandlers\AbstractHandler::open
*/
public function testOpen()
{
$handler = new ArrayHandler();
$return = $handler->open('/foo/bar', '1337asd098hkl7654');
$this->assertTrue($return);
$this->assertEquals('1337asd098hkl7654', $handler->getName());
$this->assertEquals('/foo/bar', $handler->getSessionPath());
}
/**
* @covers \Engelsystem\Http\SessionHandlers\AbstractHandler::close
*/
public function testClose()
{
$handler = new ArrayHandler();
$return = $handler->close();
$this->assertTrue($return);
}
/**
* @covers \Engelsystem\Http\SessionHandlers\AbstractHandler::gc
*/
public function testGc()
{
$handler = new ArrayHandler();
$return = $handler->gc(60 * 60 * 24);
$this->assertTrue($return);
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace Engelsystem\Test\Unit\Http\SessionHandlers;
use Engelsystem\Http\SessionHandlers\DatabaseHandler;
use Engelsystem\Test\Unit\HasDatabase;
use PHPUnit\Framework\TestCase;
class DatabaseHandlerTest extends TestCase
{
use HasDatabase;
/**
* @covers \Engelsystem\Http\SessionHandlers\DatabaseHandler::__construct
* @covers \Engelsystem\Http\SessionHandlers\DatabaseHandler::read
* @covers \Engelsystem\Http\SessionHandlers\DatabaseHandler::getQuery
*/
public function testRead()
{
$handler = new DatabaseHandler($this->database);
$this->assertEquals('', $handler->read('foo'));
$this->database->insert("INSERT INTO sessions VALUES ('foo', 'Lorem Ipsum', CURRENT_TIMESTAMP)");
$this->assertEquals('Lorem Ipsum', $handler->read('foo'));
}
/**
* @covers \Engelsystem\Http\SessionHandlers\DatabaseHandler::write
* @covers \Engelsystem\Http\SessionHandlers\DatabaseHandler::getCurrentTimestamp
*/
public function testWrite()
{
$handler = new DatabaseHandler($this->database);
foreach (['Lorem Ipsum', 'Dolor Sit!'] as $data) {
$this->assertTrue($handler->write('foo', $data));
$return = $this->database->select('SELECT * FROM sessions WHERE id = :id', ['id' => 'foo']);
$this->assertCount(1, $return);
$return = array_shift($return);
$this->assertEquals($data, $return->payload);
}
}
/**
* @covers \Engelsystem\Http\SessionHandlers\DatabaseHandler::destroy
*/
public function testDestroy()
{
$this->database->insert("INSERT INTO sessions VALUES ('foo', 'Lorem Ipsum', CURRENT_TIMESTAMP)");
$this->database->insert("INSERT INTO sessions VALUES ('bar', 'Dolor Sit', CURRENT_TIMESTAMP)");
$handler = new DatabaseHandler($this->database);
$this->assertTrue($handler->destroy('batz'));
$return = $this->database->select('SELECT * FROM sessions');
$this->assertCount(2, $return);
$this->assertTrue($handler->destroy('bar'));
$return = $this->database->select('SELECT * FROM sessions');
$this->assertCount(1, $return);
$return = array_shift($return);
$this->assertEquals('foo', $return->id);
}
/**
* @covers \Engelsystem\Http\SessionHandlers\DatabaseHandler::gc
*/
public function testGc()
{
$this->database->insert("INSERT INTO sessions VALUES ('foo', 'Lorem Ipsum', '2000-01-01 01:00')");
$this->database->insert("INSERT INTO sessions VALUES ('bar', 'Dolor Sit', '3000-01-01 01:00')");
$handler = new DatabaseHandler($this->database);
$this->assertTrue($handler->gc(60 * 60));
$return = $this->database->select('SELECT * FROM sessions');
$this->assertCount(1, $return);
$return = array_shift($return);
$this->assertEquals('bar', $return->id);
}
/**
* Prepare tests
*/
protected function setUp()
{
$this->initDatabase();
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Engelsystem\Test\Unit\Http\SessionHandlers\Stub;
use Engelsystem\Http\SessionHandlers\AbstractHandler;
class ArrayHandler extends AbstractHandler
{
/** @var string[] */
protected $content = [];
/**
* {@inheritdoc}
*/
public function read($id): string
{
if (isset($this->content[$id])) {
return $this->content[$id];
}
return '';
}
/**
* {@inheritdoc}
*/
public function write($id, $data): bool
{
$this->content[$id] = $data;
return true;
}
/**
* {@inheritdoc}
*/
public function destroy($id): bool
{
unset($this->content[$id]);
return true;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return string
*/
public function getSessionPath(): string
{
return $this->sessionPath;
}
}

View File

@ -2,7 +2,9 @@
namespace Engelsystem\Test\Unit\Http; namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Config\Config;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\SessionHandlers\DatabaseHandler;
use Engelsystem\Http\SessionServiceProvider; use Engelsystem\Http\SessionServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest; use Engelsystem\Test\Unit\ServiceProviderTest;
use PHPUnit_Framework_MockObject_MockObject as MockObject; use PHPUnit_Framework_MockObject_MockObject as MockObject;
@ -23,6 +25,9 @@ class SessionServiceProviderTest extends ServiceProviderTest
$sessionStorage = $this->getMockForAbstractClass(StorageInterface::class); $sessionStorage = $this->getMockForAbstractClass(StorageInterface::class);
$sessionStorage2 = $this->getMockForAbstractClass(StorageInterface::class); $sessionStorage2 = $this->getMockForAbstractClass(StorageInterface::class);
$databaseHandler = $this->getMockBuilder(DatabaseHandler::class)
->disableOriginalConstructor()
->getMock();
$session = $this->getSessionMock(); $session = $this->getSessionMock();
$request = $this->getRequestMock(); $request = $this->getRequestMock();
@ -32,22 +37,38 @@ class SessionServiceProviderTest extends ServiceProviderTest
->setConstructorArgs([$app]) ->setConstructorArgs([$app])
->setMethods(['isCli']) ->setMethods(['isCli'])
->getMock(); ->getMock();
$serviceProvider->expects($this->exactly(2))
->method('isCli')
->willReturnOnConsecutiveCalls(true, false);
$app->expects($this->exactly(4)) /** @var Config|MockObject $config */
$config = $this->createMock(Config::class);
$serviceProvider->expects($this->exactly(3))
->method('isCli')
->willReturnOnConsecutiveCalls(true, false, false);
$app->expects($this->exactly(7))
->method('make') ->method('make')
->withConsecutive( ->withConsecutive(
[MockArraySessionStorage::class], [MockArraySessionStorage::class],
[Session::class], [Session::class],
[NativeSessionStorage::class, ['options' => ['cookie_httponly' => true]]], [
NativeSessionStorage::class,
['options' => ['cookie_httponly' => true, 'name' => 'session'], 'handler' => null]
],
[Session::class],
[DatabaseHandler::class],
[
NativeSessionStorage::class,
['options' => ['cookie_httponly' => true, 'name' => 'foobar'], 'handler' => $databaseHandler]
],
[Session::class] [Session::class]
) )
->willReturnOnConsecutiveCalls( ->willReturnOnConsecutiveCalls(
$sessionStorage, $sessionStorage,
$session, $session,
$sessionStorage2, $sessionStorage2,
$session,
$databaseHandler,
$sessionStorage2,
$session $session
); );
$app->expects($this->atLeastOnce()) $app->expects($this->atLeastOnce())
@ -58,13 +79,38 @@ class SessionServiceProviderTest extends ServiceProviderTest
['session', $session] ['session', $session]
); );
$app->expects($this->exactly(5))
->method('get')
->withConsecutive(
['request'],
['config'],
['request'],
['config'],
['request']
)
->willReturnOnConsecutiveCalls(
$request,
$config,
$request,
$config,
$request
);
$config->expects($this->exactly(2))
->method('get')
->with('session')
->willReturnOnConsecutiveCalls(
['driver' => 'native', 'name' => 'session'],
['driver' => 'pdo', 'name' => 'foobar']
);
$this->setExpects($app, 'bind', [StorageInterface::class, 'session.storage'], null, $this->atLeastOnce()); $this->setExpects($app, 'bind', [StorageInterface::class, 'session.storage'], null, $this->atLeastOnce());
$this->setExpects($app, 'get', ['request'], $request, $this->atLeastOnce());
$this->setExpects($request, 'setSession', [$session], null, $this->atLeastOnce()); $this->setExpects($request, 'setSession', [$session], null, $this->atLeastOnce());
$this->setExpects($session, 'start', null, null, $this->atLeastOnce()); $this->setExpects($session, 'start', null, null, $this->atLeastOnce());
$serviceProvider->register(); $serviceProvider->register();
$serviceProvider->register(); $serviceProvider->register();
$serviceProvider->register();
} }
/** /**

View File

@ -3,6 +3,8 @@
namespace Engelsystem\Test\Unit\Models; namespace Engelsystem\Test\Unit\Models;
use Engelsystem\Test\Unit\Models\Stub\BaseModelImplementation; use Engelsystem\Test\Unit\Models\Stub\BaseModelImplementation;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class BaseModelTest extends TestCase class BaseModelTest extends TestCase
@ -19,4 +21,26 @@ class BaseModelTest extends TestCase
$this->assertEquals('bar', $newModel->foo); $this->assertEquals('bar', $newModel->foo);
$this->assertEquals(1, $newModel->saveCount); $this->assertEquals(1, $newModel->saveCount);
} }
/**
* @covers \Engelsystem\Models\BaseModel::find
*/
public function testFind()
{
/** @var QueryBuilder|MockObject $queryBuilder */
$queryBuilder = $this->createMock(QueryBuilder::class);
BaseModelImplementation::$queryBuilder = $queryBuilder;
$anotherModel = new BaseModelImplementation();
$queryBuilder->expects($this->once())
->method('find')
->with(1337, ['foo', 'bar'])
->willReturn($anotherModel);
$model = new BaseModelImplementation();
$newModel = $model->find(1337, ['foo', 'bar']);
$this->assertEquals($anotherModel, $newModel);
}
} }

View File

@ -3,6 +3,7 @@
namespace Engelsystem\Test\Unit\Models\Stub; namespace Engelsystem\Test\Unit\Models\Stub;
use Engelsystem\Models\BaseModel; use Engelsystem\Models\BaseModel;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
/** /**
* @property string foo * @property string foo
@ -15,6 +16,9 @@ class BaseModelImplementation extends BaseModel
/** @var int */ /** @var int */
public $saveCount = 0; public $saveCount = 0;
/** @var QueryBuilder */
public static $queryBuilder = null;
/** /**
* @param array $options * @param array $options
* @return bool * @return bool
@ -24,4 +28,12 @@ class BaseModelImplementation extends BaseModel
$this->saveCount++; $this->saveCount++;
return true; return true;
} }
/**
* @return QueryBuilder
*/
public static function query()
{
return self::$queryBuilder;
}
} }