From 004a44402207ff026d625a942dcbdd2a2ee5ded6 Mon Sep 17 00:00:00 2001 From: Tony NGUEREZA Date: Mon, 20 Nov 2023 20:17:45 +0100 Subject: [PATCH] Add maintenance tests --- src/Helper/CsvReader.php | 3 - src/Http/Middleware/MaintenanceMiddleware.php | 4 +- .../Command/MaintenanceCommandTest.php | 213 +++++++++++- tests/Env/EnvTest.php | 10 + .../Driver/FileMaintenanceDriverTest.php | 58 ++++ .../Middleware/MaintenanceMiddlewareTest.php | 302 ++++++++++++++++++ tests/Security/Csrf/CsrfManagerTest.php | 3 + tests/fixtures/fixtures.php | 61 ++++ tests/fixtures/mocks.php | 13 + 9 files changed, 658 insertions(+), 9 deletions(-) create mode 100644 tests/Http/Maintenance/Driver/FileMaintenanceDriverTest.php create mode 100644 tests/Http/Middleware/MaintenanceMiddlewareTest.php diff --git a/src/Helper/CsvReader.php b/src/Helper/CsvReader.php index e40e78b..841ad38 100644 --- a/src/Helper/CsvReader.php +++ b/src/Helper/CsvReader.php @@ -196,9 +196,6 @@ public function parse(): self $i = 0; while (($data = fgetcsv($fp, $this->limit, $this->delimiter)) !== false) { - if ($data === null) { - continue; - } // skip all empty lines if ($data[0] !== null) { if ($i === 0) { diff --git a/src/Http/Middleware/MaintenanceMiddleware.php b/src/Http/Middleware/MaintenanceMiddleware.php index abf8952..f764354 100644 --- a/src/Http/Middleware/MaintenanceMiddleware.php +++ b/src/Http/Middleware/MaintenanceMiddleware.php @@ -223,10 +223,12 @@ protected function hasValidBypassCookie(ServerRequestInterface $request, array $ if (!isset($data['secret'])) { return false; } + + $secret = $data['secret']; $name = $this->getCookieName(); $cookieValue = (new RequestData($request))->cookie($name); - if ($cookieValue === null) { + if (empty($cookieValue)) { return false; } diff --git a/tests/Console/Command/MaintenanceCommandTest.php b/tests/Console/Command/MaintenanceCommandTest.php index 2603bbe..0056887 100644 --- a/tests/Console/Command/MaintenanceCommandTest.php +++ b/tests/Console/Command/MaintenanceCommandTest.php @@ -4,14 +4,13 @@ namespace Platine\Test\Framework\Console\Command; -use Platine\Config\Config; -use Platine\Console\Application as ConsoleApp; -use Platine\Console\IO\Interactor; use Platine\Framework\App\Application; use Platine\Framework\Console\Command\MaintenanceCommand; use Platine\Test\Framework\Console\BaseCommandTestCase; use RuntimeException; +use function Platine\Test\Framework\Fixture\getTestMaintenanceDriver; + /* * @group core * @group framework @@ -55,7 +54,7 @@ public function testExecuteWrongArgument(): void $o = new MaintenanceCommand($app, $mockInfo[3]); $o->bind($mockInfo[0]); - $this->expectExceptionMessage(RuntimeException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Invalid argument type [not-found], must be one of [up, down, status]'); $o->parse(['platine', 'not-found']); @@ -103,7 +102,7 @@ public function testExecuteWrongRetryValue(): void $o = new MaintenanceCommand($app, $mockInfo[3]); $o->bind($mockInfo[0]); - $this->expectExceptionMessage(RuntimeException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Invalid retry value [0], must be an integer greather than zero'); $o->parse(['platine', 'down', '-r=0']); @@ -112,4 +111,208 @@ public function testExecuteWrongRetryValue(): void $o->interact($mockInfo[1], $mockInfo[2]); $o->execute(); } + + public function testExecuteWrongRefreshValue(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url() + ]); + + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Invalid refresh value [0], must be an integer greather than zero'); + + $o->parse(['platine', 'down', '-e=0']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + } + + public function testExecuteWrongHttpStatusValue(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url() + ]); + + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Invalid HTTP status value [100], must be between 200 and 505'); + + $o->parse(['platine', 'down', '-c=100']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + } + + public function testExecuteStatusDown(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url(), + 'isInMaintenance' => true, + ]); + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $o->parse(['platine', 'status']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + + $expected = 'APPLICATION MAINTENANCE MANAGEMENT + +Application is down. +'; + $this->assertEquals($expected, $this->getConsoleOutputContent()); + } + + public function testExecuteDownAlreadyInMaintenance(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url(), + 'isInMaintenance' => true, + ]); + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $o->parse(['platine', 'down']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + + $expected = 'APPLICATION MAINTENANCE MANAGEMENT + +Application is already down. +'; + $this->assertEquals($expected, $this->getConsoleOutputContent()); + } + + public function testExecuteDownSuccess(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url(), + 'isInMaintenance' => false, + ]); + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $o->parse(['platine', 'down', '-r=100', '-e=1000', '-c=300', '-m="Hello World"']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + + $expected = 'APPLICATION MAINTENANCE MANAGEMENT + +Application is now in maintenance mode. +'; + $this->assertEquals($expected, $this->getConsoleOutputContent()); + } + + public function testExecuteOnlineSuccess(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url(), + 'isInMaintenance' => true, + ]); + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $o->parse(['platine', 'up']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + + $expected = 'APPLICATION MAINTENANCE MANAGEMENT + +Application is now online +'; + $this->assertEquals($expected, $this->getConsoleOutputContent()); + } + + public function testExecuteOnlineError(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url(), + 'isInMaintenance' => true, + 'maintenance' => getTestMaintenanceDriver(true, true), + ]); + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $o->parse(['platine', 'up']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + + $expected = 'APPLICATION MAINTENANCE MANAGEMENT + +Failed to disable maintenance mode: Maintenance deactivate error. +'; + $this->assertEquals($expected, $this->getConsoleOutputContent()); + } + + public function testExecuteDownError(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $app = $this->getMockInstance(Application::class, [ + 'getAppPath' => $dir->url(), + 'isInMaintenance' => false, + 'maintenance' => getTestMaintenanceDriver(true, false), + ]); + + $mockInfo = $this->getConsoleApp(); + + $o = new MaintenanceCommand($app, $mockInfo[3]); + $o->bind($mockInfo[0]); + + $o->parse(['platine', 'down']); + $this->assertEquals('maintenance', $o->getName()); + + $o->interact($mockInfo[1], $mockInfo[2]); + $o->execute(); + + $expected = 'APPLICATION MAINTENANCE MANAGEMENT + +Failed to enable maintenance mode: Maintenance activate error. +'; + $this->assertEquals($expected, $this->getConsoleOutputContent()); + } } diff --git a/tests/Env/EnvTest.php b/tests/Env/EnvTest.php index 6bc124b..c50fdf8 100644 --- a/tests/Env/EnvTest.php +++ b/tests/Env/EnvTest.php @@ -44,4 +44,14 @@ public function testGetUsingResolved(): void $this->assertEquals('foo', Env::get('server_key1')); $this->assertEquals('foo/bar', Env::get('server_key2')); } + + public function testGetUsingArgumentValue(): void + { + global $mock_preg_replace_callback_to_null; + $mock_preg_replace_callback_to_null = true; + $_SERVER['server_key1'] = 'foo'; + $_SERVER['server_key2'] = '${server_keyX}/bar'; + $this->assertEquals('foo', Env::get('server_key1')); + $this->assertEquals('${server_keyX}/bar', Env::get('server_key2')); + } } diff --git a/tests/Http/Maintenance/Driver/FileMaintenanceDriverTest.php b/tests/Http/Maintenance/Driver/FileMaintenanceDriverTest.php new file mode 100644 index 0000000..21a46fb --- /dev/null +++ b/tests/Http/Maintenance/Driver/FileMaintenanceDriverTest.php @@ -0,0 +1,58 @@ +vfsRoot = vfsStream::setup(); + $this->vfsPath = vfsStream::newDirectory('my_tests')->at($this->vfsRoot); + } + + public function testDefault(): void + { + $dir = $this->createVfsDirectory('app', $this->vfsRoot); + $localAdapter = new LocalAdapter($dir->url()); + $filesystem = new Filesystem($localAdapter); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.storages.file.path', '', $dir->url() . '/maintenance'] + ] + ]); + + $o = new FileMaintenanceDriver($config, $filesystem); + $this->assertFalse($o->active()); + + $o->activate(['foo' => 'bar']); + + $this->assertTrue($o->active()); + + $data = $o->data(); + $this->assertCount(1, $data); + $this->assertArrayHasKey('foo', $data); + $this->assertEquals('bar', $data['foo']); + + $o->deactivate(); + $this->assertFalse($o->active()); + } +} diff --git a/tests/Http/Middleware/MaintenanceMiddlewareTest.php b/tests/Http/Middleware/MaintenanceMiddlewareTest.php new file mode 100644 index 0000000..ece45c6 --- /dev/null +++ b/tests/Http/Middleware/MaintenanceMiddlewareTest.php @@ -0,0 +1,302 @@ +getMockInstance(Application::class); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.cookie.name', 'platine_maintenance', 'platine_maintenance'], + ['maintenance.url_whitelist', [], ['/foo/*']], + ] + ]); + + $uri = $this->getMockInstance(Uri::class, [ + 'getPath' => '/foo/bar', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getUri' => $uri, + ]); + $handler = $this->getMockInstance(HttpKernel::class); + + $o = new MaintenanceMiddleware($config, $app); + $res = $o->process($request, $handler); + + $this->assertEquals(0, $res->getStatusCode()); + } + + public function testProcessApplicationIsOnline(): void + { + $app = $this->getMockInstance(Application::class); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.cookie.name', 'platine_maintenance', 'platine_maintenance'], + ['maintenance.url_whitelist', [], []], + ] + ]); + + $uri = $this->getMockInstance(Uri::class, [ + 'getPath' => '/foo/bar', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getUri' => $uri, + ]); + $handler = $this->getMockInstance(HttpKernel::class); + + $o = new MaintenanceMiddleware($config, $app); + $res = $o->process($request, $handler); + + $this->assertEquals(0, $res->getStatusCode()); + } + + public function testProcessThrowExceptionGetData(): void + { + $app = $this->getMockInstance(Application::class, [ + 'isInMaintenance' => true, + 'maintenance' => getTestMaintenanceDriver(true, true, true), + ]); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.cookie.name', 'platine_maintenance', 'platine_maintenance'], + ['maintenance.url_whitelist', [], []], + ] + ]); + + $uri = $this->getMockInstance(Uri::class, [ + 'getPath' => '/foo/bar', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getUri' => $uri, + ]); + $handler = $this->getMockInstance(HttpKernel::class); + + $o = new MaintenanceMiddleware($config, $app); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Maintenance data error'); + $res = $o->process($request, $handler); + } + + public function testProcessBypassRoute(): void + { + $app = $this->getMockInstanceMap(Application::class, [ + 'isInMaintenance' => [[true]], + 'maintenance' => [[getTestMaintenanceDriver(false, true, false)]], + 'get' => [ + [ + RouteHelper::class, + $this->getMockInstance(RouteHelper::class, [ + 'generateUrl' => '/foo/home' + ]) + ], + [ + CookieManager::class, + $this->getMockInstance(CookieManager::class, [ + + ]) + ], + ] + ]); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.cookie.name', 'platine_maintenance', 'platine_maintenance'], + ['maintenance.bypass_route', '', 'platine_maintenance'], + ['maintenance.url_whitelist', [], []], + ] + ]); + + $uri = $this->getMockInstance(Uri::class, [ + 'getPath' => '/08685bd7-594b-4ce1-9a6b-f5d168ecdb05', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getUri' => $uri, + ]); + $handler = $this->getMockInstance(HttpKernel::class); + + $o = new MaintenanceMiddleware($config, $app); + + $res = $o->process($request, $handler); + $this->assertEquals(0, $res->getStatusCode()); + } + + public function testProcessUsingCookieBypassSuccess(): void + { + $app = $this->getMockInstanceMap(Application::class, [ + 'isInMaintenance' => [[true]], + 'maintenance' => [[getTestMaintenanceDriver(false, true, false)]], + 'get' => [ + [ + RouteHelper::class, + $this->getMockInstance(RouteHelper::class, [ + 'generateUrl' => '/foo/home' + ]) + ], + [ + CookieManager::class, + $this->getMockInstance(CookieManager::class, [ + + ]) + ], + ] + ]); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.cookie.name', 'platine_maintenance', 'platine_maintenance'], + ['maintenance.bypass_route', '', 'platine_maintenance'], + ['maintenance.url_whitelist', [], []], + ] + ]); + + $uri = $this->getMockInstance(Uri::class, [ + 'getPath' => '/foo/bar', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getUri' => $uri, + 'getCookieParams' => ['platine_maintenance' => $this->getCookieTestValue()], + ]); + $handler = $this->getMockInstance(HttpKernel::class); + + $o = new MaintenanceMiddleware($config, $app); + + $res = $o->process($request, $handler); + $this->assertEquals(0, $res->getStatusCode()); + } + + public function testProcessThrowServiceUnavailable(): void + { + $app = $this->getMockInstanceMap(Application::class, [ + 'isInMaintenance' => [[true]], + 'maintenance' => [[getTestMaintenanceDriver(false, true, false, ['secret', 'template'])]], + 'get' => [ + [ + RouteHelper::class, + $this->getMockInstance(RouteHelper::class, [ + 'generateUrl' => '/foo/home' + ]) + ], + [ + CookieManager::class, + $this->getMockInstance(CookieManager::class, [ + + ]) + ], + ] + ]); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.cookie.name', 'platine_maintenance', 'platine_maintenance'], + ['maintenance.bypass_route', '', 'platine_maintenance'], + ['maintenance.url_whitelist', [], []], + ] + ]); + + $uri = $this->getMockInstance(Uri::class, [ + 'getPath' => '/foo/bar', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getUri' => $uri, + 'getCookieParams' => ['platine_maintenance' => $this->getCookieTestValue()], + ]); + $handler = $this->getMockInstance(HttpKernel::class); + + $o = new MaintenanceMiddleware($config, $app); + + $this->expectException(HttpException::class); + $this->expectExceptionMessage('Please the system is upgrading'); + $res = $o->process($request, $handler); + } + + public function testProcessThrowServiceUnavailableUsingTemplate(): void + { + $app = $this->getMockInstanceMap(Application::class, [ + 'isInMaintenance' => [[true]], + 'maintenance' => [[getTestMaintenanceDriver(false, true, false, [])]], + 'get' => [ + [ + RouteHelper::class, + $this->getMockInstance(RouteHelper::class, [ + 'generateUrl' => '/foo/home' + ]) + ], + [ + CookieManager::class, + $this->getMockInstance(CookieManager::class, [ + + ]) + ], + [ + Template::class, + $this->getMockInstance(Template::class, [ + + ]) + ], + ] + ]); + + $config = $this->getMockInstanceMap(Config::class, [ + 'get' => [ + ['maintenance.cookie.name', 'platine_maintenance', 'platine_maintenance'], + ['maintenance.bypass_route', '', 'platine_maintenance'], + ['maintenance.url_whitelist', [], []], + ] + ]); + + $uri = $this->getMockInstance(Uri::class, [ + 'getPath' => '/foo/bar', + ]); + $request = $this->getMockInstance(ServerRequest::class, [ + 'getUri' => $uri, + 'getCookieParams' => ['platine_maintenance' => null], + ]); + $handler = $this->getMockInstance(HttpKernel::class); + + $o = new MaintenanceMiddleware($config, $app); + + $res = $o->process($request, $handler); + $this->assertInstanceOf(TemplateResponse::class, $res); + $this->assertEquals(503, $res->getStatusCode()); + } + + protected function getCookieTestValue(): string + { + $secret = '08685bd7-594b-4ce1-9a6b-f5d168ecdb05'; + $expire = time() + 10000; + + $data = [ + 'expire' => $expire, + 'hash' => hash_hmac('sha256', (string) $expire, $secret) + ]; + return base64_encode(Json::encode($data)); + } +} diff --git a/tests/Security/Csrf/CsrfManagerTest.php b/tests/Security/Csrf/CsrfManagerTest.php index 685e4e8..7f57ecd 100644 --- a/tests/Security/Csrf/CsrfManagerTest.php +++ b/tests/Security/Csrf/CsrfManagerTest.php @@ -92,6 +92,9 @@ public function testValidateRequestTokenNotFound(): void $request = $this->getMockInstance(ServerRequest::class); $this->assertFalse($o->validate($request)); + + $o->clear(); + $this->assertFalse($o->validate($request)); } public function testValidateRequestTokenNotMatch(): void diff --git a/tests/fixtures/fixtures.php b/tests/fixtures/fixtures.php index 211b297..4d531ae 100644 --- a/tests/fixtures/fixtures.php +++ b/tests/fixtures/fixtures.php @@ -16,6 +16,7 @@ use Platine\Framework\Config\DatabaseConfigLoader; use Platine\Framework\Form\Param\BaseParam; use Platine\Framework\Form\Validator\AbstractValidator; +use Platine\Framework\Http\Maintenance\MaintenanceDriverInterface; use Platine\Framework\Http\RouteHelper; use Platine\Framework\Security\Csrf\CsrfManager; use Platine\Framework\Service\ServiceProvider; @@ -44,6 +45,66 @@ use Platine\Validator\Rule\NotEmpty; use Traversable; +function getTestMaintenanceDriver(bool $exception = false, bool $active = false, bool $dataException = false, array $excludes = []): MaintenanceDriverInterface +{ + return new class ($exception, $active, $dataException, $excludes) implements MaintenanceDriverInterface{ + protected bool $exception = true; + protected bool $active = true; + protected bool $dataException = true; + protected array $excludes = []; + + public function __construct(bool $exception = true, bool $active = true, bool $dataException = true, array $excludes = []) + { + $this->exception = $exception; + $this->active = $active; + $this->dataException = $dataException; + $this->excludes = $excludes; + } + + public function activate($data): void + { + if ($this->exception) { + throw new Exception('Maintenance activate error'); + } + } + + public function active(): bool + { + return $this->active; + } + + public function data(): array + { + if ($this->dataException) { + throw new Exception('Maintenance data error'); + } + + $data = [ + 'except' => [], + 'template' => 'maintenance', + 'retry' => 1080, + 'refresh' => 15, + 'secret' => '08685bd7-594b-4ce1-9a6b-f5d168ecdb05', + 'status' => 503, + 'message' => 'Please the system is upgrading', + ]; + + foreach ($this->excludes as $key) { + unset($data[$key]); + } + + return $data; + } + + public function deactivate(): void + { + if ($this->exception) { + throw new Exception('Maintenance deactivate error'); + } + } + }; +} + class MyOAuthGrant extends BaseGrant { public function allowPublicClients(): bool diff --git a/tests/fixtures/mocks.php b/tests/fixtures/mocks.php index 587418b..922317e 100644 --- a/tests/fixtures/mocks.php +++ b/tests/fixtures/mocks.php @@ -145,6 +145,18 @@ function base64_decode(string $string, bool $strict = false) $mock_parse_ini_string_to_false = false; $mock_getenv_to_foo = false; +$mock_preg_replace_callback_to_null = false; + +function preg_replace_callback($pattern, callable $callback, $subject, int $limit = -1, &$count = null, int $flags = 0) +{ + global $mock_preg_replace_callback_to_null; + if ($mock_preg_replace_callback_to_null) { + return null; + } + + return \preg_replace_callback($pattern, $callback, $subject, $limit, $count, $flags); +} + function parse_ini_string(string $ini_string, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL) { @@ -511,6 +523,7 @@ function hash_equals($known_string, $user_string) $mock_file_exists_to_false = false; $mock_fopen_to_false = false; + function file_exists(string $str) { global $mock_file_exists_to_false;