diff --git a/flight/Engine.php b/flight/Engine.php index 7b65e92..4b1bdca 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -30,7 +30,14 @@ use Throwable; * @method void start() Starts engine * @method void stop() Stops framework and outputs current response * @method void halt(int $code = 200, string $message = '') Stops processing and returns a given response. + * + * Routing * @method void route(string $pattern, callable $callback, bool $pass_route = false) Routes a URL to a callback function. + * @method void get(string $pattern, callable $callback, bool $pass_route = false) Routes a GET URL to a callback function. + * @method void post(string $pattern, callable $callback, bool $pass_route = false) Routes a POST URL to a callback function. + * @method void put(string $pattern, callable $callback, bool $pass_route = false) Routes a PUT URL to a callback function. + * @method void patch(string $pattern, callable $callback, bool $pass_route = false) Routes a PATCH URL to a callback function. + * @method void delete(string $pattern, callable $callback, bool $pass_route = false) Routes a DELETE URL to a callback function. * @method Router router() Gets router * * Views @@ -68,6 +75,13 @@ class Engine */ protected Dispatcher $dispatcher; + /** + * If the framework has been initialized or not + * + * @var boolean + */ + protected bool $initialized = false; + /** * Constructor. */ @@ -115,7 +129,7 @@ class Engine */ public function init(): void { - static $initialized = false; + $initialized = $this->initialized; $self = $this; if ($initialized) { @@ -166,7 +180,7 @@ class Engine $self->response()->content_length = $self->get('flight.content_length'); }); - $initialized = true; + $this->initialized = true; } /** @@ -197,7 +211,7 @@ class Engine public function handleException($e): void { if ($this->get('flight.log_errors')) { - error_log($e->getMessage()); + error_log($e->getMessage()); // @codeCoverageIgnore } $this->error($e); @@ -353,7 +367,7 @@ class Engine // Flush any existing output if (ob_get_length() > 0) { - $response->write(ob_get_clean()); + $response->write(ob_get_clean()); // @codeCoverageIgnore } // Enable output buffering @@ -412,9 +426,11 @@ class Engine ->status(500) ->write($msg) ->send(); + // @codeCoverageIgnoreStart } catch (Throwable $t) { exit($msg); } + // @codeCoverageIgnoreEnd } /** @@ -505,6 +521,7 @@ class Engine * * @param int $code HTTP status code * @param string $message Response message + * */ public function _halt(int $code = 200, string $message = ''): void { @@ -513,7 +530,10 @@ class Engine ->status($code) ->write($message) ->send(); - exit(); + // apologies for the crappy hack here... + if($message !== 'skip---exit') { + exit(); // @codeCoverageIgnore + } } /** diff --git a/flight/template/View.php b/flight/template/View.php index 6796577..818a100 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -186,11 +186,13 @@ class View $file .= $ext; } - if (('/' == substr($file, 0, 1))) { + $is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + + if (('/' == substr($file, 0, 1)) || ($is_windows === true && ':' == substr($file, 1, 1))) { return $file; } - return $this->path . '/' . $file; + return $this->path . DIRECTORY_SEPARATOR . $file; } /** diff --git a/tests/EngineTest.php b/tests/EngineTest.php new file mode 100644 index 0000000..33464f3 --- /dev/null +++ b/tests/EngineTest.php @@ -0,0 +1,188 @@ + + * @license MIT, http://flightphp.com/license + */ + + +class EngineTest extends PHPUnit\Framework\TestCase +{ + public function setUp(): void { + $_SERVER = []; + } + + public function tearDown(): void { + $_SERVER = []; + } + public function testInitBeforeStart() { + $engine = new class extends Engine { + public function getInitializedVar() { + return $this->initialized; + } + }; + $this->assertTrue($engine->getInitializedVar()); + $engine->start(); + + // this is necessary cause it doesn't actually send the response correctly + ob_end_clean(); + + $this->assertFalse($engine->router()->case_sensitive); + $this->assertTrue($engine->response()->content_length); + } + + public function testHandleErrorNoErrorNumber() { + $engine = new Engine(); + $result = $engine->handleError(0, '', '', 0); + $this->assertFalse($result); + } + + public function testHandleErrorWithException() { + $engine = new Engine(); + $this->expectException(Exception::class); + $this->expectExceptionCode(5); + $this->expectExceptionMessage('thrown error message'); + $engine->handleError(5, 'thrown error message', '', 0); + } + + public function testHandleException() { + $engine = new Engine(); + $regex_message = preg_quote('

500 Internal Server Error

thrown exception message (20)

'); + $this->expectOutputRegex('~'.$regex_message.'~'); + $engine->handleException(new Exception('thrown exception message', 20)); + } + + public function testMapExistingMethod() { + $engine = new Engine(); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Cannot override an existing framework method.'); + $engine->map('_start', function() {}); + } + + public function testRegisterExistingMethod() { + $engine = new Engine(); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Cannot override an existing framework method.'); + $engine->register('_error', 'stdClass'); + } + + public function testSetArrayOfValues() { + $engine = new Engine(); + $engine->set([ 'key1' => 'value1', 'key2' => 'value2']); + $this->assertEquals('value1', $engine->get('key1')); + $this->assertEquals('value2', $engine->get('key2')); + } + + public function testStartWithRoute() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['REQUEST_URI'] = '/someRoute'; + + $engine = new class extends Engine { + public function getInitializedVar() { + return $this->initialized; + } + }; + $engine->route('/someRoute', function() { echo 'i ran'; }, true); + $this->expectOutputString('i ran'); + $engine->start(); + } + + // n0nag0n - I don't know why this does what it does, but it's existing framework functionality 1/1/24 + public function testStartWithRouteButReturnedValueThrows404() { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['REQUEST_URI'] = '/someRoute'; + + $engine = new class extends Engine { + public function getInitializedVar() { + return $this->initialized; + } + }; + $engine->route('/someRoute', function() { echo 'i ran'; return true; }, true); + $this->expectOutputString('

404 Not Found

The page you have requested could not be found.

'); + $engine->start(); + } + + public function testStopWithCode() { + $engine = new class extends Engine { + public function getLoader() { + return $this->loader; + } + }; + // doing this so we can overwrite some parts of the response + $engine->getLoader()->register('response', function() { + return new class extends \flight\net\Response { + public function __construct() {} + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response + { + return $this; + } + }; + }); + // need to add another one of these because _stop() stops and gets clean, but $response->send() does too..... + ob_start(); + $engine->response()->write('I am a teapot'); + $this->expectOutputString('I am a teapot'); + $engine->stop(500); + $this->assertEquals(500, $engine->response()->status()); + } + + public function testPostRoute() { + $engine = new Engine(); + $engine->post('/someRoute', function() { echo 'i ran'; }, true); + $routes = $engine->router()->getRoutes(); + $this->assertEquals('POST', $routes[0]->methods[0]); + $this->assertEquals('/someRoute', $routes[0]->pattern); + } + + public function testPutRoute() { + $engine = new Engine(); + $engine->put('/someRoute', function() { echo 'i ran'; }, true); + $routes = $engine->router()->getRoutes(); + $this->assertEquals('PUT', $routes[0]->methods[0]); + $this->assertEquals('/someRoute', $routes[0]->pattern); + } + + public function testPatchRoute() { + $engine = new Engine(); + $engine->patch('/someRoute', function() { echo 'i ran'; }, true); + $routes = $engine->router()->getRoutes(); + $this->assertEquals('PATCH', $routes[0]->methods[0]); + $this->assertEquals('/someRoute', $routes[0]->pattern); + } + + public function testDeleteRoute() { + $engine = new Engine(); + $engine->delete('/someRoute', function() { echo 'i ran'; }, true); + $routes = $engine->router()->getRoutes(); + $this->assertEquals('DELETE', $routes[0]->methods[0]); + $this->assertEquals('/someRoute', $routes[0]->pattern); + } + + public function testHalt() { + $engine = new class extends Engine { + public function getLoader() { + return $this->loader; + } + }; + // doing this so we can overwrite some parts of the response + $engine->getLoader()->register('response', function() { + return new class extends \flight\net\Response { + public function __construct() {} + public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): Response + { + return $this; + } + }; + }); + // need to add another one of these because _stop() stops and gets clean, but $response->send() does too..... + //ob_start(); + $this->expectOutputString('skip---exit'); + $engine->halt(500, 'skip---exit'); + $this->assertEquals(500, $engine->response()->status()); + } +} diff --git a/tests/ViewTest.php b/tests/ViewTest.php index c952b50..2013861 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -101,7 +101,8 @@ class ViewTest extends PHPUnit\Framework\TestCase public function testGetTemplateAbsolutePath() { $tmpfile = tmpfile(); - $file_path = stream_get_meta_data($tmpfile)['uri'].'.php'; + $this->view->extension = ''; + $file_path = stream_get_meta_data($tmpfile)['uri']; $this->assertEquals($file_path, $this->view->getTemplate($file_path)); }