'Array', ]); $this->logger = Log::engine('error_test'); } /** * Teardown */ public function tearDown(): void { parent::tearDown(); Log::drop('error_test'); } /** * Test returning a response works ok. */ public function testNoErrorResponse(): void { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(); $result = $middleware->process($request, new TestRequestHandler()); $this->assertInstanceOf(Response::class, $result); $this->assertCount(0, $this->logger->read()); } /** * Test using a factory method to make a renderer. */ public function testRendererFactory(): void { $request = ServerRequestFactory::fromGlobals(); $factory = function ($exception) { $this->assertInstanceOf('LogicException', $exception); $response = new Response(); $mock = $this->getMockBuilder(ExceptionRendererInterface::class) ->getMock(); $mock->expects($this->once()) ->method('render') ->will($this->returnValue($response)); return $mock; }; $middleware = new ErrorHandlerMiddleware(new ExceptionTrap([ 'exceptionRenderer' => $factory, ])); $handler = new TestRequestHandler(function (): void { throw new LogicException('Something bad'); }); $middleware->process($request, $handler); } /** * Test rendering an error page */ public function testHandleException(): void { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(); $handler = new TestRequestHandler(function (): void { throw new NotFoundException('whoops'); }); $result = $middleware->process($request, $handler); $this->assertInstanceOf('Cake\Http\Response', $result); $this->assertSame(404, $result->getStatusCode()); $this->assertStringContainsString('was not found', '' . $result->getBody()); } /** * Test rendering an error page with an exception trap */ public function testHandleExceptionWithExceptionTrap(): void { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(new ExceptionTrap([ 'exceptionRenderer' => ExceptionRenderer::class, ])); $handler = new TestRequestHandler(function (): void { throw new NotFoundException('whoops'); }); $result = $middleware->process($request, $handler); $this->assertInstanceOf('Cake\Http\Response', $result); $this->assertSame(404, $result->getStatusCode()); $this->assertStringContainsString('was not found', '' . $result->getBody()); } /** * Test creating a redirect response */ public function testHandleRedirectException(): void { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(); $handler = new TestRequestHandler(function (): void { throw new RedirectException('http://example.org/login'); }); $result = $middleware->process($request, $handler); $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertSame(302, $result->getStatusCode()); $this->assertEmpty((string)$result->getBody()); $expected = [ 'location' => ['http://example.org/login'], ]; $this->assertSame($expected, $result->getHeaders()); } /** * Test creating a redirect response */ public function testHandleRedirectExceptionHeaders(): void { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(); $handler = new TestRequestHandler(function () { $err = new RedirectException('http://example.org/login', 301, ['Constructor' => 'yes', 'Method' => 'yes']); throw $err; }); $result = $middleware->process($request, $handler); $this->assertInstanceOf(ResponseInterface::class, $result); $this->assertSame(301, $result->getStatusCode()); $this->assertEmpty('' . $result->getBody()); $expected = [ 'location' => ['http://example.org/login'], 'Constructor' => ['yes'], 'Method' => ['yes'], ]; $this->assertEquals($expected, $result->getHeaders()); } /** * Test rendering an error page holds onto the original request. */ public function testHandleExceptionPreserveRequest(): void { $request = ServerRequestFactory::fromGlobals(); $request = $request->withHeader('Accept', 'application/json'); $middleware = new ErrorHandlerMiddleware(); $handler = new TestRequestHandler(function (): void { throw new NotFoundException('whoops'); }); $result = $middleware->process($request, $handler); $this->assertInstanceOf('Cake\Http\Response', $result); $this->assertSame(404, $result->getStatusCode()); $this->assertStringContainsString('"message": "whoops"', (string)$result->getBody()); $this->assertStringContainsString('application/json', $result->getHeaderLine('Content-type')); } /** * Test handling PHP 7's Error instance. */ public function testHandlePHP7Error(): void { $middleware = new ErrorHandlerMiddleware(); $request = ServerRequestFactory::fromGlobals(); $error = new Error(); $result = $middleware->handleException($error, $request); $this->assertInstanceOf(Response::class, $result); } /** * Test rendering an error page logs errors */ public function testHandleExceptionLogAndTrace(): void { $request = ServerRequestFactory::fromGlobals([ 'REQUEST_URI' => '/target/url', 'HTTP_REFERER' => '/other/path', ]); $middleware = new ErrorHandlerMiddleware(['log' => true, 'trace' => true]); $handler = new TestRequestHandler(function (): void { throw new NotFoundException('Kaboom!'); }); $result = $middleware->process($request, $handler); $this->assertSame(404, $result->getStatusCode()); $this->assertStringContainsString('was not found', '' . $result->getBody()); $logs = $this->logger->read(); $this->assertCount(1, $logs); $this->assertStringContainsString('error', $logs[0]); $this->assertStringContainsString('[Cake\Http\Exception\NotFoundException] Kaboom!', $logs[0]); $this->assertStringContainsString( str_replace('/', DS, 'vendor/phpunit/phpunit/src/Framework/TestCase.php'), $logs[0] ); $this->assertStringContainsString('Request URL: /target/url', $logs[0]); $this->assertStringContainsString('Referer URL: /other/path', $logs[0]); $this->assertStringNotContainsString('Previous:', $logs[0]); } /** * Test rendering an error page logs errors with previous */ public function testHandleExceptionLogAndTraceWithPrevious(): void { $request = ServerRequestFactory::fromGlobals([ 'REQUEST_URI' => '/target/url', 'HTTP_REFERER' => '/other/path', ]); $middleware = new ErrorHandlerMiddleware(['log' => true, 'trace' => true]); $handler = new TestRequestHandler(function ($req): void { $previous = new RecordNotFoundException('Previous logged'); throw new NotFoundException('Kaboom!', null, $previous); }); $result = $middleware->process($request, $handler); $this->assertSame(404, $result->getStatusCode()); $this->assertStringContainsString('was not found', '' . $result->getBody()); $logs = $this->logger->read(); $this->assertCount(1, $logs); $this->assertStringContainsString('error', $logs[0]); $this->assertStringContainsString('[Cake\Http\Exception\NotFoundException] Kaboom!', $logs[0]); $this->assertStringContainsString( 'Caused by: [Cake\Datasource\Exception\RecordNotFoundException]', $logs[0] ); $this->assertStringContainsString( str_replace('/', DS, 'vendor/phpunit/phpunit/src/Framework/TestCase.php'), $logs[0] ); $this->assertStringContainsString('Request URL: /target/url', $logs[0]); $this->assertStringContainsString('Referer URL: /other/path', $logs[0]); } /** * Test rendering an error page skips logging for specific classes */ public function testHandleExceptionSkipLog(): void { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware([ 'log' => true, 'skipLog' => ['Cake\Http\Exception\NotFoundException'], ]); $handler = new TestRequestHandler(function (): void { throw new NotFoundException('Kaboom!'); }); $result = $middleware->process($request, $handler); $this->assertSame(404, $result->getStatusCode()); $this->assertStringContainsString('was not found', '' . $result->getBody()); $this->assertCount(0, $this->logger->read()); } /** * Test rendering an error page logs exception attributes */ public function testHandleExceptionLogAttributes(): void { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(['log' => true]); $handler = new TestRequestHandler(function (): void { throw new MissingControllerException(['class' => 'Articles']); }); $result = $middleware->process($request, $handler); $this->assertSame(404, $result->getStatusCode()); $logs = $this->logger->read(); $this->assertStringContainsString( '[Cake\Http\Exception\MissingControllerException] Controller class Articles could not be found.', $logs[0] ); $this->assertStringContainsString('Exception Attributes:', $logs[0]); $this->assertStringContainsString("'class' => 'Articles'", $logs[0]); $this->assertStringContainsString('Request URL:', $logs[0]); } /** * Test handling an error and having rendering fail. */ public function testHandleExceptionRenderingFails(): void { $request = ServerRequestFactory::fromGlobals(); $factory = function ($exception) { $mock = $this->getMockBuilder(ExceptionRendererInterface::class) ->getMock(); $mock->expects($this->once()) ->method('render') ->will($this->throwException(new LogicException('Rendering failed'))); return $mock; }; $middleware = new ErrorHandlerMiddleware(new ExceptionTrap([ 'exceptionRenderer' => $factory, ])); $handler = new TestRequestHandler(function (): void { throw new ServiceUnavailableException('whoops'); }); $response = $middleware->process($request, $handler); $this->assertSame(500, $response->getStatusCode()); $this->assertSame('An Internal Server Error Occurred', '' . $response->getBody()); } /** * Test exception args are not ignored in php7.4 with debug enabled. */ public function testExceptionArgs(): void { // Force exception_ignore_args to true for test ini_set('zend.exception_ignore_args', '1'); // Debug disabled Configure::write('debug', false); new ErrorHandlerMiddleware(); $this->assertSame('1', ini_get('zend.exception_ignore_args')); // Debug enabled Configure::write('debug', true); new ErrorHandlerMiddleware(); $this->assertSame('0', ini_get('zend.exception_ignore_args')); } }