diff --git a/.gitignore b/.gitignore
index d712148b..eb12eae7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,12 +14,14 @@ Thumbs.db
_vimrc_local.vim
.sass-cache
-# PHPstorm config
+# PHPstorm files
/.idea/
+/.phpstorm.meta.php
# Project files
/config/config.php
/test/coverage
+/public/coverage
# Composer files
/vendor/
diff --git a/composer.json b/composer.json
index 45dce626..35956e20 100644
--- a/composer.json
+++ b/composer.json
@@ -17,7 +17,9 @@
"php": ">=7.0.0",
"erusev/parsedown": "1.6.*",
"twbs/bootstrap": "^3.3",
- "symfony/http-foundation": "^3.3"
+ "symfony/http-foundation": "^3.3",
+ "psr/container": "^1.0",
+ "psr/log": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^6.3"
diff --git a/config/config.default.php b/config/config.default.php
index 419b02d1..c2d742ef 100644
--- a/config/config.default.php
+++ b/config/config.default.php
@@ -44,6 +44,9 @@ return [
// Number of News shown on one site
'display_news' => 6,
+ // Users are able to sign up
+ 'registration_enabled' => true,
+
// Only arrived angels can sign up for shifts
'signup_requires_arrival' => false,
diff --git a/db/update.sql b/db/update.sql
index 5d93e230..3ed37ceb 100644
--- a/db/update.sql
+++ b/db/update.sql
@@ -28,3 +28,6 @@ UPDATE `Groups` SET UID = UID * 10;
INSERT INTO `Groups` (Name, UID) VALUES ('News Admin', -65);
INSERT INTO `Privileges` (id, name, `desc`) VALUES (42, 'admin_news_html', 'Use HTML in news');
INSERT INTO `GroupPrivileges` (group_id, privilege_id) VALUES (-65, 14), (-65, 42);
+
+-- Add log level to LogEntries
+ALTER TABLE `LogEntries` CHANGE COLUMN `nick` `level` VARCHAR(20) NOT NULL;
diff --git a/includes/engelsystem_provider.php b/includes/engelsystem_provider.php
index aed331d4..0de5e0f5 100644
--- a/includes/engelsystem_provider.php
+++ b/includes/engelsystem_provider.php
@@ -1,24 +1,36 @@
instance('config', $config);
$config->set(require __DIR__ . '/../config/config.default.php');
if (file_exists(__DIR__ . '/../config/config.php')) {
@@ -37,7 +49,8 @@ date_default_timezone_set($config->get('timezone'));
* @var Request $request
*/
$request = Request::createFromGlobals();
-$request::setInstance($request);
+$app->instance('request', $request);
+
/**
* Check for maintenance
@@ -48,18 +61,26 @@ if ($config->get('maintenance')) {
}
+/**
+ * Register UrlGenerator
+ */
+$urlGenerator = new UrlGenerator();
+$app->instance('routing.urlGenerator', $urlGenerator);
+
+
/**
* Initialize renderer
*/
$renderer = new Renderer();
+$app->instance('renderer', $renderer);
$renderer->addRenderer(new HtmlEngine());
-Renderer::setInstance($renderer);
/**
* Register error handler
*/
$errorHandler = new ExceptionHandler();
+$app->instance('error.handler', $errorHandler);
if (config('environment') == 'development') {
$errorHandler->setEnvironment(ExceptionHandler::ENV_DEVELOPMENT);
ini_set('display_errors', true);
@@ -80,6 +101,14 @@ Db::connect(
Db::getPdo()->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Db::getPdo()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+/**
+ * Init logger
+ */
+$logger = new EngelsystemLogger();
+$app->instance('logger', $logger);
+$app->instance(LoggerInterface::class, $logger);
+$app->instance(EngelsystemLogger::class, $logger);
+
/**
* Include legacy code
@@ -170,7 +199,9 @@ foreach ($includeFiles as $file) {
/**
* Init application
*/
-$session = new Session();
+$sessionStorage = (PHP_SAPI != 'cli' ? new NativeSessionStorage(['cookie_httponly' => true]) : new MockArraySessionStorage());
+$session = new Session($sessionStorage);
+$app->instance('session', $session);
$session->start();
$request->setSession($session);
diff --git a/includes/helper/internationalization_helper.php b/includes/helper/internationalization_helper.php
index 131941e9..efbe5db5 100644
--- a/includes/helper/internationalization_helper.php
+++ b/includes/helper/internationalization_helper.php
@@ -1,7 +1,5 @@
$name) {
diff --git a/includes/model/LogEntries_model.php b/includes/model/LogEntries_model.php
index 0e11bf8e..f0ee6673 100644
--- a/includes/model/LogEntries_model.php
+++ b/includes/model/LogEntries_model.php
@@ -5,16 +5,16 @@ use Engelsystem\Database\DB;
/**
* Creates a log entry.
*
- * @param string $nick Username
- * @param string $message Log Message
+ * @param string $logLevel Log level
+ * @param string $message Log Message
* @return bool
*/
-function LogEntry_create($nick, $message)
+function LogEntry_create($logLevel, $message)
{
return DB::insert('
- INSERT INTO `LogEntries` (`timestamp`, `nick`, `message`)
+ INSERT INTO `LogEntries` (`timestamp`, `level`, `message`)
VALUES(?, ?, ?)
- ', [time(), $nick, $message]);
+ ', [time(), $logLevel, $message]);
}
/**
@@ -43,7 +43,7 @@ function LogEntries_filter($keyword)
return DB::select('
SELECT *
FROM `LogEntries`
- WHERE `nick` LIKE ?
+ WHERE `level` LIKE ?
OR `message` LIKE ?
ORDER BY `timestamp` DESC
',
diff --git a/includes/pages/admin_log.php b/includes/pages/admin_log.php
index 03c9abb0..694b1d5a 100644
--- a/includes/pages/admin_log.php
+++ b/includes/pages/admin_log.php
@@ -17,12 +17,10 @@ function admin_log()
if (request()->has('keyword')) {
$filter = strip_request_item('keyword');
}
- $log_entries_source = LogEntries_filter($filter);
+ $log_entries = LogEntries_filter($filter);
- $log_entries = [];
- foreach ($log_entries_source as $log_entry) {
+ foreach ($log_entries as &$log_entry) {
$log_entry['date'] = date('d.m.Y H:i', $log_entry['timestamp']);
- $log_entries[] = $log_entry;
}
return page_with_title(admin_log_title(), [
@@ -33,7 +31,7 @@ function admin_log()
]),
table([
'date' => 'Time',
- 'nick' => 'Angel',
+ 'level' => 'Type',
'message' => 'Log Entry'
], $log_entries)
]);
diff --git a/includes/pages/guest_login.php b/includes/pages/guest_login.php
index 9179c6c4..0577951f 100644
--- a/includes/pages/guest_login.php
+++ b/includes/pages/guest_login.php
@@ -33,7 +33,7 @@ function logout_title()
*/
function guest_register()
{
- global $user;
+ global $user, $privileges;
$tshirt_sizes = config('tshirt_sizes');
$enable_tshirt_size = config('enable_tshirt_size');
$min_password_length = config('min_password_length');
@@ -75,6 +75,14 @@ function guest_register()
}
}
+ if (!in_array('register', $privileges) || (!isset($user) && !config('registration_enabled'))) {
+ error(_('Registration is disabled.'));
+
+ return page_with_title(register_title(), [
+ msg(),
+ ]);
+ }
+
if ($request->has('submit')) {
$valid = true;
@@ -496,7 +504,7 @@ function get_register_hint()
{
global $privileges;
- if (in_array('register', $privileges)) {
+ if (in_array('register', $privileges) && config('registration_enabled')) {
return join('', [
'
' . _('Please sign up, if you want to help us!') . '
',
buttons([
diff --git a/includes/pages/user_atom.php b/includes/pages/user_atom.php
index 2991bdbf..c9d9398e 100644
--- a/includes/pages/user_atom.php
+++ b/includes/pages/user_atom.php
@@ -1,7 +1,6 @@
Engelsystem
diff --git a/includes/sys_log.php b/includes/sys_log.php
index c4ef890e..513586e6 100644
--- a/includes/sys_log.php
+++ b/includes/sys_log.php
@@ -9,10 +9,12 @@
function engelsystem_log($message)
{
global $user;
-
$nick = "Guest";
+ $logger = app('logger');
+
if (isset($user)) {
$nick = User_Nick_render($user);
}
- LogEntry_create($nick, $message);
+
+ $logger->info('{nick}: {message}', ['nick' => $nick, 'message' => $message]);
}
diff --git a/includes/sys_menu.php b/includes/sys_menu.php
index 2eaa1234..385a6948 100644
--- a/includes/sys_menu.php
+++ b/includes/sys_menu.php
@@ -59,7 +59,7 @@ function header_toolbar()
);
}
- if (!isset($user) && in_array('register', $privileges)) {
+ if (!isset($user) && in_array('register', $privileges) && config('registration_enabled')) {
$toolbar_items[] = toolbar_item_link(page_link_to('register'), 'plus', register_title(), $page == 'register');
}
diff --git a/includes/view/AngelTypes_view.php b/includes/view/AngelTypes_view.php
index f75cc616..baf0e04a 100644
--- a/includes/view/AngelTypes_view.php
+++ b/includes/view/AngelTypes_view.php
@@ -475,13 +475,27 @@ function AngelTypes_about_view_angeltype($angeltype)
*/
function AngelTypes_about_view($angeltypes, $user_logged_in)
{
+ global $privileges;
+
+ $buttons = [];
+
+ if ($user_logged_in) {
+ $buttons[] = button(page_link_to('angeltypes'), angeltypes_title(), 'back');
+ } else {
+ if (in_array('register', $privileges) && config('registration_enabled')) {
+ $buttons[] = button(page_link_to('register'), register_title());
+ }
+
+ $buttons[] = button(page_link_to('login'), login_title());
+ }
+
+ $faqUrl = config('faq_url');
+ if (!empty($faqUrl)) {
+ $buttons[] = button($faqUrl, _('FAQ'), 'btn-primary');
+ }
+
$content = [
- buttons([
- !$user_logged_in ? button(page_link_to('register'), register_title()) : '',
- !$user_logged_in ? button(page_link_to('login'), login_title()) : '',
- $user_logged_in ? button(page_link_to('angeltypes'), angeltypes_title(), 'back') : '',
- button(config('faq_url'), _('FAQ'), 'btn-primary')
- ]),
+ buttons($buttons),
'' . _('Here is the list of teams and their tasks. If you have questions, read the FAQ.') . '
',
'
'
];
diff --git a/phpunit.xml b/phpunit.xml
index ff6eb120..bdc4b0b6 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,17 +1,19 @@
-
- ./test/model/
+
+ ./tests/Feature
+
+
+ ./tests/Unit
./include/
- ./public/
./src/
diff --git a/public/.htaccess b/public/.htaccess
index c9d40187..2b1d3a7a 100644
--- a/public/.htaccess
+++ b/public/.htaccess
@@ -1,3 +1,4 @@
+
RewriteEngine on
diff --git a/public/index.php b/public/index.php
index b44e1491..c65dbdf8 100644
--- a/public/index.php
+++ b/public/index.php
@@ -1,7 +1,5 @@
query->get('p');
if (empty($page)) {
$page = $request->path();
diff --git a/src/Application.php b/src/Application.php
new file mode 100644
index 00000000..674b3869
--- /dev/null
+++ b/src/Application.php
@@ -0,0 +1,25 @@
+registerBaseBindings();
+ }
+
+ protected function registerBaseBindings()
+ {
+ self::setInstance($this);
+ Container::setInstance($this);
+ $this->instance('app', $this);
+ $this->instance('container', $this);
+ $this->instance(Container::class, $this);
+ $this->instance(Application::class, $this);
+ $this->instance(ContainerInterface::class, $this);
+ }
+}
diff --git a/src/Config/Config.php b/src/Config/Config.php
index 02080de4..34c21a78 100644
--- a/src/Config/Config.php
+++ b/src/Config/Config.php
@@ -2,15 +2,8 @@
namespace Engelsystem\Config;
-use ErrorException;
-
class Config
{
- /**
- * @var self
- */
- protected static $instance;
-
/**
* The config values
*
@@ -104,25 +97,4 @@ class Config
{
$this->remove($key);
}
-
- /**
- * @return Config
- * @throws ErrorException
- */
- public static function getInstance()
- {
- if (!self::$instance instanceof self) {
- throw new ErrorException('Config not initialized');
- }
-
- return self::$instance;
- }
-
- /**
- * @param self $instance
- */
- public static function setInstance($instance)
- {
- self::$instance = $instance;
- }
}
diff --git a/src/Container/Container.php b/src/Container/Container.php
new file mode 100644
index 00000000..59a17a04
--- /dev/null
+++ b/src/Container/Container.php
@@ -0,0 +1,116 @@
+has($id)) {
+ return $this->resolve($id);
+ }
+
+ throw new NotFoundException(sprintf('The entry with the id "%s" could not be found', $id));
+ }
+
+ /**
+ * Register a shared entry in the container
+ *
+ * @param string $abstract Identifier of the entry to set
+ * @param mixed $instance Entry
+ */
+ public function instance($abstract, $instance)
+ {
+ $this->singleton($abstract, $instance);
+ }
+
+ /**
+ * Register a shared entry as singleton in the container
+ *
+ * @param string $abstract
+ * @param mixed $instance
+ */
+ public function singleton($abstract, $instance)
+ {
+ $this->instances[$abstract] = $instance;
+ }
+
+ /**
+ * Returns true if the container can return an entry for the given identifier
+ * Returns false otherwise
+ *
+ * `has($id)` returning true does not mean that `get($id)` will not throw an exception
+ * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`
+ *
+ * @param string $id Identifier of the entry to look for
+ *
+ * @return bool
+ */
+ public function has($id)
+ {
+ return isset($this->instances[$id]);
+ }
+
+ /**
+ * Resolve the requested object
+ *
+ * @param string $abstract
+ * @return mixed
+ */
+ protected function resolve($abstract)
+ {
+ return $this->instances[$abstract];
+ }
+
+ /**
+ * Get the globally available instance of the container
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (is_null(static::$instance)) {
+ static::$instance = new static;
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * Set the globally available instance of the container
+ *
+ * @param Container $container
+ */
+ public static function setInstance(Container $container)
+ {
+ static::$instance = $container;
+ }
+}
diff --git a/src/Container/ContainerException.php b/src/Container/ContainerException.php
new file mode 100644
index 00000000..3cdde506
--- /dev/null
+++ b/src/Container/ContainerException.php
@@ -0,0 +1,11 @@
+handle(
'exception',
diff --git a/src/Http/Request.php b/src/Http/Request.php
index f0235d45..e7850c8b 100644
--- a/src/Http/Request.php
+++ b/src/Http/Request.php
@@ -2,14 +2,10 @@
namespace Engelsystem\Http;
-use ErrorException;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class Request extends SymfonyRequest
{
- /** @var self */
- protected static $instance;
-
/**
* Get POST input
*
@@ -68,25 +64,4 @@ class Request extends SymfonyRequest
{
return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
}
-
- /**
- * @return self
- * @throws ErrorException
- */
- public static function getInstance()
- {
- if (!self::$instance instanceof self) {
- throw new ErrorException('Request not initialized');
- }
-
- return self::$instance;
- }
-
- /**
- * @param self $instance
- */
- public static function setInstance($instance)
- {
- self::$instance = $instance;
- }
}
diff --git a/src/Logger/EngelsystemLogger.php b/src/Logger/EngelsystemLogger.php
new file mode 100644
index 00000000..1f255b69
--- /dev/null
+++ b/src/Logger/EngelsystemLogger.php
@@ -0,0 +1,74 @@
+checkLevel($level)) {
+ throw new InvalidArgumentException();
+ }
+
+ $message = $this->interpolate($message, $context);
+
+ LogEntry_create($level, $message);
+ }
+
+ /**
+ * Interpolates context values into the message placeholders.
+ *
+ * @param string $message
+ * @param array $context
+ * @return string
+ */
+ protected function interpolate($message, array $context = [])
+ {
+ foreach ($context as $key => $val) {
+ // check that the value can be casted to string
+ if (is_array($val) || (is_object($val) && !method_exists($val, '__toString'))) {
+ continue;
+ }
+
+ // replace the values of the message
+ $message = str_replace('{' . $key . '}', $val, $message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param string $level
+ * @return bool
+ */
+ protected function checkLevel($level)
+ {
+ return in_array($level, $this->allowedLevels);
+ }
+}
diff --git a/src/Renderer/HtmlEngine.php b/src/Renderer/HtmlEngine.php
index 4a48e1f0..75343bbd 100644
--- a/src/Renderer/HtmlEngine.php
+++ b/src/Renderer/HtmlEngine.php
@@ -29,6 +29,6 @@ class HtmlEngine implements EngineInterface
*/
public function canRender($path)
{
- return strpos($path, '.html') && file_exists($path);
+ return strpos($path, '.htm') && file_exists($path);
}
}
diff --git a/src/Renderer/Renderer.php b/src/Renderer/Renderer.php
index bf3d5609..de31ca74 100644
--- a/src/Renderer/Renderer.php
+++ b/src/Renderer/Renderer.php
@@ -2,12 +2,11 @@
namespace Engelsystem\Renderer;
-use ErrorException;
+use Psr\Log\LoggerAwareTrait;
class Renderer
{
- /** @var self */
- protected static $instance;
+ use LoggerAwareTrait;
/** @var EngineInterface[] */
protected $renderer = [];
@@ -29,7 +28,10 @@ class Renderer
return $renderer->get($template, $data);
}
- engelsystem_error('Unable to find a renderer for template file «' . $template . '».');
+ if ($this->logger) {
+ $this->logger->error('Unable to find a renderer for template file "{file}"', ['file' => $template]);
+ }
+
return '';
}
@@ -42,21 +44,4 @@ class Renderer
{
$this->renderer[] = $renderer;
}
-
- /**
- * @return self
- * @throws ErrorException
- */
- public static function getInstance()
- {
- return self::$instance;
- }
-
- /**
- * @param self $instance
- */
- public static function setInstance($instance)
- {
- self::$instance = $instance;
- }
}
diff --git a/src/Routing/UrlGenerator.php b/src/Routing/UrlGenerator.php
index 8dc464c6..6df52425 100644
--- a/src/Routing/UrlGenerator.php
+++ b/src/Routing/UrlGenerator.php
@@ -2,8 +2,6 @@
namespace Engelsystem\Routing;
-use Engelsystem\Http\Request;
-
class UrlGenerator
{
/**
@@ -11,10 +9,10 @@ class UrlGenerator
* @param array $parameters
* @return string
*/
- public static function to($path, $parameters = [])
+ public function to($path, $parameters = [])
{
$path = '/' . ltrim($path, '/');
- $request = Request::getInstance();
+ $request = app('request');
$uri = $request->getUriForPath($path);
if (!empty($parameters) && is_array($parameters)) {
diff --git a/src/helpers.php b/src/helpers.php
index 24f93f2c..de303963 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -1,12 +1,28 @@
get($id);
+}
+
/**
* Get or set config values
*
@@ -16,15 +32,18 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
*/
function config($key = null, $default = null)
{
+ $config = app('config');
+
if (empty($key)) {
- return Config::getInstance();
+ return $config;
}
if (is_array($key)) {
- Config::getInstance()->set($key);
+ $config->set($key);
+ return true;
}
- return Config::getInstance()->get($key, $default);
+ return $config->get($key, $default);
}
/**
@@ -34,7 +53,7 @@ function config($key = null, $default = null)
*/
function request($key = null, $default = null)
{
- $request = Request::getInstance();
+ $request = app('request');
if (is_null($key)) {
return $request;
@@ -50,7 +69,7 @@ function request($key = null, $default = null)
*/
function session($key = null, $default = null)
{
- $session = request()->getSession();
+ $session = app('session');
if (is_null($key)) {
return $session;
@@ -66,7 +85,7 @@ function session($key = null, $default = null)
*/
function view($template = null, $data = null)
{
- $renderer = Renderer::getInstance();
+ $renderer = app('renderer');
if (is_null($template)) {
return $renderer;
@@ -78,9 +97,15 @@ function view($template = null, $data = null)
/**
* @param string $path
* @param array $parameters
- * @return string
+ * @return UrlGenerator|string
*/
-function url($path, $parameters = [])
+function url($path = null, $parameters = [])
{
- return UrlGenerator::to($path, $parameters);
+ $urlGenerator = app('routing.urlGenerator');
+
+ if (is_null($path)) {
+ return $urlGenerator;
+ }
+
+ return $urlGenerator->to($path, $parameters);
}
diff --git a/test/model/LogEntriesModelTest.php b/test/model/LogEntriesModelTest.php
deleted file mode 100644
index 25d46fc4..00000000
--- a/test/model/LogEntriesModelTest.php
+++ /dev/null
@@ -1,38 +0,0 @@
-assertNotFalse(LogEntry_create('test', 'test_LogEntry_create'));
-
- // There should be one more log entry now
- $this->assertEquals(count(LogEntries()), $count + 1);
- }
-
- public function test_LogEntries_clear_all()
- {
- $this->create_LogEntry();
- $this->assertTrue(count(LogEntries()) > 0);
- $this->assertNotFalse(LogEntries_clear_all());
- $this->assertEquals(count(LogEntries()), 0);
- }
-
- /**
- * @after
- */
- public function teardown()
- {
- LogEntries_clear_all();
- }
-}
diff --git a/tests/Feature/Logger/EngelsystemLoggerTest.php b/tests/Feature/Logger/EngelsystemLoggerTest.php
new file mode 100644
index 00000000..63a01318
--- /dev/null
+++ b/tests/Feature/Logger/EngelsystemLoggerTest.php
@@ -0,0 +1,150 @@
+assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @return string[]
+ */
+ public function provideLogLevels()
+ {
+ return [
+ [LogLevel::ALERT],
+ [LogLevel::CRITICAL],
+ [LogLevel::DEBUG],
+ [LogLevel::EMERGENCY],
+ [LogLevel::ERROR],
+ [LogLevel::INFO],
+ [LogLevel::NOTICE],
+ [LogLevel::WARNING],
+ ];
+ }
+
+ /**
+ * @dataProvider provideLogLevels
+ * @param string $level
+ */
+ public function testAllLevels($level)
+ {
+ $logger = $this->getLogger();
+
+ LogEntries_clear_all();
+
+ $logger->log($level, 'First log message');
+ $logger->{$level}('Second log message');
+
+ $entries = LogEntries();
+ $this->assertCount(2, $entries);
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ LogEntries_clear_all();
+
+ $logger->log(LogLevel::INFO, 'My username is {username}', ['username' => 'Foo']);
+
+ $entry = $this->getLastEntry();
+ $this->assertEquals('My username is Foo', $entry['message']);
+ $this->assertEquals(LogLevel::INFO, $entry['level']);
+ }
+
+ /**
+ * @return string[]
+ */
+ public function provideContextReplaceValues()
+ {
+ return [
+ ['Data and {context}', [], 'Data and {context}'],
+ ['Data and {context}', ['context' => null], 'Data and '],
+ ['Data and {context}', ['context' => new \stdClass()], 'Data and {context}'],
+ ['Some user asked: {question}', ['question' => 'Foo?'], 'Some user asked: Foo?'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideContextReplaceValues
+ *
+ * @param string $message
+ * @param string[] $context
+ * @param string $expected
+ */
+ public function testContextReplaceValues($message, $context, $expected)
+ {
+ $logger = $this->getLogger();
+ $logger->log(LogLevel::INFO, $message, $context);
+
+ $entry = $this->getLastEntry();
+ $this->assertEquals($expected, $entry['message']);
+ }
+
+ public function testContextToString()
+ {
+ $logger = $this->getLogger();
+ LogEntries_clear_all();
+
+ $mock = $this->getMockBuilder('someDataProvider')
+ ->setMethods(['__toString'])
+ ->getMock();
+
+ $mock->expects($this->atLeastOnce())
+ ->method('__toString')
+ ->will($this->returnValue('FooBar'));
+
+ $logger->log(LogLevel::INFO, 'Some data and {context}', ['context' => $mock]);
+
+ $entry = $this->getLastEntry();
+ $this->assertEquals('Some data and FooBar', $entry['message']);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testThrowExceptionOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+
+ $logger->log('This log level should never be defined', 'Some message');
+ }
+
+ /**
+ * @return array
+ */
+ public function getLastEntry()
+ {
+ $entries = LogEntries();
+ $entry = array_pop($entries);
+
+ return $entry;
+ }
+
+ public function tearDown()
+ {
+ LogEntries_clear_all();
+ }
+}
diff --git a/tests/Feature/model/LogEntriesModelTest.php b/tests/Feature/model/LogEntriesModelTest.php
new file mode 100644
index 00000000..6d7b0ebc
--- /dev/null
+++ b/tests/Feature/model/LogEntriesModelTest.php
@@ -0,0 +1,38 @@
+assertNotFalse(LogEntry_create(LogLevel::WARNING, 'test_LogEntry_create'));
+
+ // There should be one more log entry now
+ $this->assertEquals(count(LogEntries()), $count + 1);
+ }
+
+ public function testClearAllLogEntries()
+ {
+ LogEntry_create(LogLevel::WARNING, 'test');
+ $this->assertTrue(count(LogEntries()) > 0);
+
+ $this->assertNotFalse(LogEntries_clear_all());
+ $this->assertCount(0, LogEntries());
+ }
+
+ public function tearDown()
+ {
+ LogEntries_clear_all();
+ }
+}
diff --git a/test/model/RoomModelTest.php b/tests/Feature/model/RoomModelTest.php
similarity index 69%
rename from test/model/RoomModelTest.php
rename to tests/Feature/model/RoomModelTest.php
index 135a6108..96be84a2 100644
--- a/test/model/RoomModelTest.php
+++ b/tests/Feature/model/RoomModelTest.php
@@ -1,13 +1,18 @@
room_id = Room_create('test', false, true, '');
@@ -16,20 +21,17 @@ class RoomModelTest extends TestCase
public function test_Room()
{
$this->create_Room();
-
+
$room = Room($this->room_id);
-
+
$this->assertNotFalse($room);
$this->assertNotNull($room);
$this->assertEquals($room['Name'], 'test');
-
- $this->assertNull(Room(- 1));
+
+ $this->assertNull(Room(-1));
}
- /**
- * @after
- */
- public function teardown()
+ public function tearDown()
{
if ($this->room_id != null) {
Room_delete($this->room_id);
diff --git a/tests/Unit/ApplicationTest.php b/tests/Unit/ApplicationTest.php
new file mode 100644
index 00000000..77429f44
--- /dev/null
+++ b/tests/Unit/ApplicationTest.php
@@ -0,0 +1,29 @@
+assertInstanceOf(Container::class, $app);
+ $this->assertInstanceOf(ContainerInterface::class, $app);
+ $this->assertSame($app, $app->get('app'));
+ $this->assertSame($app, $app->get('container'));
+ $this->assertSame($app, $app->get(Container::class));
+ $this->assertSame($app, $app->get(Application::class));
+ $this->assertSame($app, $app->get(ContainerInterface::class));
+ $this->assertSame($app, Container::getInstance());
+ }
+}
diff --git a/tests/Unit/Config/ConfigTest.php b/tests/Unit/Config/ConfigTest.php
new file mode 100644
index 00000000..ce11ebd6
--- /dev/null
+++ b/tests/Unit/Config/ConfigTest.php
@@ -0,0 +1,115 @@
+set('test', 'FooBar');
+ $this->assertEquals(['test' => 'FooBar'], $config->get(null));
+ $this->assertEquals('FooBar', $config->get('test'));
+
+ $this->assertEquals('defaultValue', $config->get('notExisting', 'defaultValue'));
+
+ $this->assertNull($config->get('notExisting'));
+ }
+
+ /**
+ * @covers \Engelsystem\Config\Config::set
+ */
+ public function testSet()
+ {
+ $config = new Config();
+
+ $config->set('test', 'FooBar');
+ $this->assertEquals('FooBar', $config->get('test'));
+
+ $config->set([
+ 'name' => 'Engelsystem',
+ 'mail' => ['user' => 'test'],
+ ]);
+ $this->assertEquals('Engelsystem', $config->get('name'));
+ $this->assertEquals(['user' => 'test'], $config->get('mail'));
+ }
+
+ /**
+ * @covers \Engelsystem\Config\Config::has
+ */
+ public function testHas()
+ {
+ $config = new Config();
+
+ $this->assertFalse($config->has('test'));
+
+ $config->set('test', 'FooBar');
+ $this->assertTrue($config->has('test'));
+ }
+
+ /**
+ * @covers \Engelsystem\Config\Config::remove
+ */
+ public function testRemove()
+ {
+ $config = new Config();
+ $config->set(['foo' => 'bar', 'test' => '123']);
+
+ $config->remove('foo');
+ $this->assertEquals(['test' => '123'], $config->get(null));
+ }
+
+ /**
+ * @covers \Engelsystem\Config\Config::__get
+ */
+ public function testMagicGet()
+ {
+ $config = new Config();
+
+ $config->set('test', 'FooBar');
+ $this->assertEquals('FooBar', $config->test);
+ }
+
+ /**
+ * @covers \Engelsystem\Config\Config::__set
+ */
+ public function testMagicSet()
+ {
+ $config = new Config();
+
+ $config->test = 'FooBar';
+ $this->assertEquals('FooBar', $config->get('test'));
+ }
+
+ /**
+ * @covers \Engelsystem\Config\Config::__isset
+ */
+ public function testMagicIsset()
+ {
+ $config = new Config();
+
+ $this->assertFalse(isset($config->test));
+
+ $config->set('test', 'FooBar');
+ $this->assertTrue(isset($config->test));
+ }
+
+ /**
+ * @covers \Engelsystem\Config\Config::__unset
+ */
+ public function testMagicUnset()
+ {
+ $config = new Config();
+ $config->set(['foo' => 'bar', 'test' => '123']);
+
+ unset($config->foo);
+ $this->assertEquals(['test' => '123'], $config->get(null));
+ }
+}
diff --git a/tests/Unit/Container/ContainerTest.php b/tests/Unit/Container/ContainerTest.php
new file mode 100644
index 00000000..89c34209
--- /dev/null
+++ b/tests/Unit/Container/ContainerTest.php
@@ -0,0 +1,104 @@
+instance('foo', $class);
+ $this->assertSame($class, $container->get('foo'));
+ }
+
+ /**
+ * @covers \Engelsystem\Container\Container::get
+ * @expectedException \Engelsystem\Container\NotFoundException
+ */
+ public function testGetException()
+ {
+ $container = new Container();
+
+ $container->get('not.registered.service');
+ }
+
+ /**
+ * @covers \Engelsystem\Container\Container::instance
+ * @covers \Engelsystem\Container\Container::resolve
+ */
+ public function testInstance()
+ {
+ $container = new Container();
+ $class = new class
+ {
+ };
+
+ $container->instance('foo', $class);
+ $this->assertSame($class, $container->get('foo'));
+ }
+
+ /**
+ * @covers \Engelsystem\Container\Container::has
+ */
+ public function testHas()
+ {
+ $container = new Container();
+
+ $this->assertFalse($container->has('test'));
+
+ $class = new class
+ {
+ };
+
+ $container->instance('test', $class);
+ $this->assertTrue($container->has('test'));
+ }
+
+ /**
+ * @covers \Engelsystem\Container\Container::singleton
+ */
+ public function testSingleton()
+ {
+ $container = new Container();
+ $class = new class
+ {
+ };
+
+ $container->singleton('foo', $class);
+ $this->assertSame($class, $container->get('foo'));
+ $this->assertSame($class, $container->get('foo'));
+ }
+
+ /**
+ * @covers \Engelsystem\Container\Container::setInstance
+ * @covers \Engelsystem\Container\Container::getInstance
+ */
+ public function testContainerSingleton()
+ {
+ // Ensure that no container has been initialized
+ $reflection = new \ReflectionProperty(Container::class, 'instance');
+ $reflection->setAccessible(true);
+ $reflection->setValue(null, null);
+ $reflection->setAccessible(false);
+
+ $container0 = new Container();
+ $container = Container::getInstance();
+
+ $this->assertNotSame($container0, $container);
+
+ $container1 = new Container;
+ Container::setInstance($container1);
+
+ $this->assertSame($container1, Container::getInstance());
+ }
+}
diff --git a/tests/Unit/HelpersTest.php b/tests/Unit/HelpersTest.php
new file mode 100644
index 00000000..d9782888
--- /dev/null
+++ b/tests/Unit/HelpersTest.php
@@ -0,0 +1,152 @@
+getAppMock('some.name', $class);
+
+ $this->assertEquals($appMock, app());
+ $this->assertEquals($class, app('some.name'));
+ }
+
+ /**
+ * @covers \config
+ */
+ public function testConfig()
+ {
+ $configMock = $this->getMockBuilder(Config::class)
+ ->getMock();
+
+ $this->getAppMock('config', $configMock);
+ $this->assertEquals($configMock, config());
+
+ $configMock->expects($this->once())
+ ->method('set')
+ ->with(['foo' => 'bar']);
+
+ $this->assertTrue(config(['foo' => 'bar']));
+
+ $configMock->expects($this->once())
+ ->method('get')
+ ->with('mail')
+ ->willReturn(['user' => 'FooBar']);
+
+ $this->assertEquals(['user' => 'FooBar'], config('mail'));
+ }
+
+ /**
+ * @covers \request
+ */
+ public function testRequest()
+ {
+ $requestMock = $this->getMockBuilder(Request::class)
+ ->getMock();
+
+ $this->getAppMock('request', $requestMock);
+ $this->assertEquals($requestMock, request());
+
+ $requestMock->expects($this->once())
+ ->method('input')
+ ->with('requestKey')
+ ->willReturn('requestValue');
+
+ $this->assertEquals('requestValue', request('requestKey'));
+ }
+
+ /**
+ * @covers \session
+ */
+ public function testSession()
+ {
+ $sessionMock = $this->getMockBuilder(Session::class)
+ ->getMock();
+
+ $this->getAppMock('session', $sessionMock);
+ $this->assertEquals($sessionMock, session());
+
+ $sessionMock->expects($this->once())
+ ->method('get')
+ ->with('someKey')
+ ->willReturn('someValue');
+
+ $this->assertEquals('someValue', session('someKey'));
+ }
+
+ /**
+ * @covers \view
+ */
+ public function testView()
+ {
+ $rendererMock = $this->getMockBuilder(Renderer::class)
+ ->getMock();
+
+ $this->getAppMock('renderer', $rendererMock);
+ $this->assertEquals($rendererMock, view());
+
+ $rendererMock->expects($this->once())
+ ->method('render')
+ ->with('template.name', ['template' => 'data'])
+ ->willReturn('rendered template');
+
+ $this->assertEquals('rendered template', view('template.name', ['template' => 'data']));
+ }
+
+ /**
+ * @covers \url
+ */
+ public function testUrl()
+ {
+ $urlGeneratorMock = $this->getMockBuilder(UrlGenerator::class)
+ ->getMock();
+
+ $this->getAppMock('routing.urlGenerator', $urlGeneratorMock);
+ $this->assertEquals($urlGeneratorMock, url());
+
+ $urlGeneratorMock->expects($this->once())
+ ->method('to')
+ ->with('foo/bar', ['param' => 'value'])
+ ->willReturn('http://lorem.ipsum/foo/bar?param=value');
+
+ $this->assertEquals('http://lorem.ipsum/foo/bar?param=value', url('foo/bar', ['param' => 'value']));
+ }
+
+ /**
+ * @param string $alias
+ * @param object $object
+ * @return Application|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected function getAppMock($alias, $object)
+ {
+ $appMock = $this->getMockBuilder(Container::class)
+ ->getMock();
+
+ $appMock->expects($this->atLeastOnce())
+ ->method('get')
+ ->with($alias)
+ ->willReturn($object);
+
+ /** @var $appMock Application */
+ Application::setInstance($appMock);
+
+ return $appMock;
+ }
+}
diff --git a/tests/Unit/Renderer/HtmlEngineTest.php b/tests/Unit/Renderer/HtmlEngineTest.php
new file mode 100644
index 00000000..0b317b72
--- /dev/null
+++ b/tests/Unit/Renderer/HtmlEngineTest.php
@@ -0,0 +1,67 @@
+createTempFile('%main_content%
');
+
+ $data = $engine->get($file, ['main_content' => 'Lorem ipsum dolor sit']);
+ $this->assertEquals('Lorem ipsum dolor sit
', $data);
+ }
+
+ /**
+ * @covers \Engelsystem\Renderer\HtmlEngine::canRender
+ */
+ public function testCanRender()
+ {
+ $engine = new HtmlEngine();
+
+ $this->assertFalse($engine->canRender('/dev/null'));
+
+ $file = $this->createTempFile();
+ $this->assertTrue($engine->canRender($file));
+
+ $htmFile = $this->createTempFile('', '.htm');
+ $this->assertTrue($engine->canRender($htmFile));
+ }
+
+ /**
+ * @param string $content
+ * @param string $extension
+ * @return string
+ */
+ protected function createTempFile($content = '', $extension = '.html')
+ {
+ $tmpFileName = tempnam(sys_get_temp_dir(), 'EngelsystemUnitTest');
+
+ $fileName = $tmpFileName . $extension;
+ rename($tmpFileName, $fileName);
+
+ file_put_contents($fileName, $content);
+
+ $this->tmpFileNames[] = $fileName;
+
+ return $fileName;
+ }
+
+ public function tearDown()
+ {
+ foreach ($this->tmpFileNames as $fileName) {
+ unlink($fileName);
+ }
+ }
+}
diff --git a/tests/Unit/Renderer/RendererTest.php b/tests/Unit/Renderer/RendererTest.php
new file mode 100644
index 00000000..b0238078
--- /dev/null
+++ b/tests/Unit/Renderer/RendererTest.php
@@ -0,0 +1,55 @@
+getMockForAbstractClass(EngineInterface::class);
+
+ $nullRenderer->expects($this->atLeastOnce())
+ ->method('canRender')
+ ->willReturn(false);
+ $renderer->addRenderer($nullRenderer);
+
+ $mockRenderer = $this->getMockForAbstractClass(EngineInterface::class);
+
+ $mockRenderer->expects($this->atLeastOnce())
+ ->method('canRender')
+ ->with('foo.template')
+ ->willReturn(true);
+
+ $mockRenderer->expects($this->atLeastOnce())
+ ->method('get')
+ ->with('foo.template', ['lorem' => 'ipsum'])
+ ->willReturn('Rendered content');
+
+ $renderer->addRenderer($mockRenderer);
+ $data = $renderer->render('foo.template', ['lorem' => 'ipsum']);
+
+ $this->assertEquals('Rendered content', $data);
+ }
+
+ public function testError()
+ {
+ $renderer = new Renderer();
+
+ $loggerMock = $this->getMockForAbstractClass(LoggerInterface::class);
+ $loggerMock
+ ->expects($this->once())
+ ->method('error');
+
+ $renderer->setLogger($loggerMock);
+
+ $data = $renderer->render('testing.template');
+ $this->assertEquals('', $data);
+ }
+}
diff --git a/tests/Unit/Routing/UrlGeneratorTest.php b/tests/Unit/Routing/UrlGeneratorTest.php
new file mode 100644
index 00000000..fc23520a
--- /dev/null
+++ b/tests/Unit/Routing/UrlGeneratorTest.php
@@ -0,0 +1,51 @@
+ 'abc', 'bla' => 'foo'], 'http://f.b/foo?test=abc&bla=foo'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideLinksTo
+ * @covers \Engelsystem\Routing\UrlGenerator::to
+ *
+ * @param string $path
+ * @param string $willReturn
+ * @param string $urlToPath
+ * @param string[] $arguments
+ * @param string $expectedUrl
+ */
+ public function testTo($urlToPath, $path, $willReturn, $arguments, $expectedUrl)
+ {
+ $app = new Container();
+ $urlGenerator = new UrlGenerator();
+ Application::setInstance($app);
+
+ $request = $this->getMockBuilder(Request::class)
+ ->getMock();
+
+ $request->expects($this->once())
+ ->method('getUriForPath')
+ ->with($path)
+ ->willReturn($willReturn);
+
+ $app->instance('request', $request);
+
+ $url = $urlGenerator->to($urlToPath, $arguments);
+ $this->assertEquals($expectedUrl, $url);
+ }
+}