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); + } +}