'Array', ]); $this->logger = Log::engine('error_test'); } /** * Teardown * * @return void */ public function tearDown(): void { parent::tearDown(); Log::drop('error_test'); } /** * Test constructor error * * @return void */ public function testConstructorInvalid() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('$errorHandler argument must be a config array or ErrorHandler'); new ErrorHandlerMiddleware('nope'); } /** * Test returning a response works ok. * * @return void */ public function testNoErrorResponse() { $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. * * @return void */ public function testRendererFactory() { $request = ServerRequestFactory::fromGlobals(); $factory = function ($exception) { $this->assertInstanceOf('LogicException', $exception); $response = new Response(); $mock = $this->getMockBuilder(ExceptionRendererInterface::class) ->setMethods(['render']) ->getMock(); $mock->expects($this->once()) ->method('render') ->will($this->returnValue($response)); return $mock; }; $middleware = new ErrorHandlerMiddleware(new ErrorHandler([ 'exceptionRenderer' => $factory, ])); $handler = new TestRequestHandler(function () { throw new LogicException('Something bad'); }); $middleware->process($request, $handler); } /** * Test rendering an error page * * @return void */ public function testHandleException() { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(); $handler = new TestRequestHandler(function () { throw new \Cake\Http\Exception\NotFoundException('whoops'); }); $result = $middleware->process($request, $handler); $this->assertInstanceOf('Cake\Http\Response', $result); $this->assertEquals(404, $result->getStatusCode()); $this->assertStringContainsString('was not found', '' . $result->getBody()); } /** * Test rendering an error page holds onto the original request. * * @return void */ public function testHandleExceptionPreserveRequest() { $request = ServerRequestFactory::fromGlobals(); $request = $request->withHeader('Accept', 'application/json'); $middleware = new ErrorHandlerMiddleware(); $handler = new TestRequestHandler(function () { throw new \Cake\Http\Exception\NotFoundException('whoops'); }); $result = $middleware->process($request, $handler); $this->assertInstanceOf('Cake\Http\Response', $result); $this->assertEquals(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. * * @return void */ public function testHandlePHP7Error() { $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 * * @return void */ public function testHandleExceptionLogAndTrace() { $request = ServerRequestFactory::fromGlobals([ 'REQUEST_URI' => '/target/url', 'HTTP_REFERER' => '/other/path', ]); $middleware = new ErrorHandlerMiddleware(['log' => true, 'trace' => true]); $handler = new TestRequestHandler(function () { throw new \Cake\Http\Exception\NotFoundException('Kaboom!'); }); $result = $middleware->process($request, $handler); $this->assertEquals(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 * * @return void */ public function testHandleExceptionLogAndTraceWithPrevious() { $request = ServerRequestFactory::fromGlobals([ 'REQUEST_URI' => '/target/url', 'HTTP_REFERER' => '/other/path', ]); $middleware = new ErrorHandlerMiddleware(['log' => true, 'trace' => true]); $handler = new TestRequestHandler(function ($req) { $previous = new \Cake\Datasource\Exception\RecordNotFoundException('Previous logged'); throw new \Cake\Http\Exception\NotFoundException('Kaboom!', null, $previous); }); $result = $middleware->process($request, $handler); $this->assertEquals(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 * * @return void */ public function testHandleExceptionSkipLog() { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware([ 'log' => true, 'skipLog' => ['Cake\Http\Exception\NotFoundException'], ]); $handler = new TestRequestHandler(function () { throw new \Cake\Http\Exception\NotFoundException('Kaboom!'); }); $result = $middleware->process($request, $handler); $this->assertEquals(404, $result->getStatusCode()); $this->assertStringContainsString('was not found', '' . $result->getBody()); $this->assertCount(0, $this->logger->read()); } /** * Test rendering an error page logs exception attributes * * @return void */ public function testHandleExceptionLogAttributes() { $request = ServerRequestFactory::fromGlobals(); $middleware = new ErrorHandlerMiddleware(['log' => true]); $handler = new TestRequestHandler(function () { throw new MissingControllerException(['class' => 'Articles']); }); $result = $middleware->process($request, $handler); $this->assertEquals(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. * * @return void */ public function testHandleExceptionRenderingFails() { $request = ServerRequestFactory::fromGlobals(); $factory = function ($exception) { $mock = $this->getMockBuilder(ExceptionRendererInterface::class) ->setMethods(['render']) ->getMock(); $mock->expects($this->once()) ->method('render') ->will($this->throwException(new LogicException('Rendering failed'))); return $mock; }; $middleware = new ErrorHandlerMiddleware(new ErrorHandler([ 'exceptionRenderer' => $factory, ])); $handler = new TestRequestHandler(function () { throw new \Cake\Http\Exception\ServiceUnavailableException('whoops'); }); $response = $middleware->process($request, $handler); $this->assertEquals(500, $response->getStatusCode()); $this->assertSame('An Internal Server Error Occurred', '' . $response->getBody()); } }