123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- <?php
- namespace Illuminate\Tests\Foundation;
- use Exception;
- use Illuminate\Config\Repository as Config;
- use Illuminate\Container\Container;
- use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract;
- use Illuminate\Contracts\Support\Responsable;
- use Illuminate\Contracts\View\Factory;
- use Illuminate\Database\RecordsNotFoundException;
- use Illuminate\Foundation\Exceptions\Handler;
- use Illuminate\Http\RedirectResponse;
- use Illuminate\Http\Request;
- use Illuminate\Routing\Redirector;
- use Illuminate\Routing\ResponseFactory;
- use Illuminate\Support\MessageBag;
- use Illuminate\Validation\ValidationException;
- use Illuminate\Validation\Validator;
- use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
- use Mockery as m;
- use PHPUnit\Framework\TestCase;
- use Psr\Log\LoggerInterface;
- use RuntimeException;
- use stdClass;
- use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
- use Symfony\Component\HttpFoundation\File\UploadedFile;
- use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
- use Symfony\Component\HttpKernel\Exception\HttpException;
- class FoundationExceptionsHandlerTest extends TestCase
- {
- use MockeryPHPUnitIntegration;
- protected $config;
- protected $container;
- protected $handler;
- protected $request;
- protected function setUp(): void
- {
- $this->config = m::mock(Config::class);
- $this->request = m::mock(stdClass::class);
- $this->container = Container::setInstance(new Container);
- $this->container->singleton('config', function () {
- return $this->config;
- });
- $this->container->singleton(ResponseFactoryContract::class, function () {
- return new ResponseFactory(
- m::mock(Factory::class),
- m::mock(Redirector::class)
- );
- });
- $this->handler = new Handler($this->container);
- }
- protected function tearDown(): void
- {
- Container::setInstance(null);
- }
- public function testHandlerReportsExceptionAsContext()
- {
- $logger = m::mock(LoggerInterface::class);
- $this->container->instance(LoggerInterface::class, $logger);
- $logger->shouldReceive('error')->withArgs(['Exception message', m::hasKey('exception')])->once();
- $this->handler->report(new RuntimeException('Exception message'));
- }
- public function testHandlerCallsContextMethodIfPresent()
- {
- $logger = m::mock(LoggerInterface::class);
- $this->container->instance(LoggerInterface::class, $logger);
- $logger->shouldReceive('error')->withArgs(['Exception message', m::subset(['foo' => 'bar'])])->once();
- $this->handler->report(new ContextProvidingException('Exception message'));
- }
- public function testHandlerReportsExceptionWhenUnReportable()
- {
- $logger = m::mock(LoggerInterface::class);
- $this->container->instance(LoggerInterface::class, $logger);
- $logger->shouldReceive('error')->withArgs(['Exception message', m::hasKey('exception')])->once();
- $this->handler->report(new UnReportableException('Exception message'));
- }
- public function testHandlerCallsReportMethodWithDependencies()
- {
- $reporter = m::mock(ReportingService::class);
- $this->container->instance(ReportingService::class, $reporter);
- $reporter->shouldReceive('send')->withArgs(['Exception message'])->once();
- $logger = m::mock(LoggerInterface::class);
- $this->container->instance(LoggerInterface::class, $logger);
- $logger->shouldNotReceive('error');
- $this->handler->report(new ReportableException('Exception message'));
- }
- public function testHandlerReportsExceptionUsingCallableClass()
- {
- $reporter = m::mock(ReportingService::class);
- $reporter->shouldReceive('send')->withArgs(['Exception message'])->once();
- $logger = m::mock(LoggerInterface::class);
- $this->container->instance(LoggerInterface::class, $logger);
- $logger->shouldNotReceive('error');
- $this->handler->reportable(new CustomReporter($reporter));
- $this->handler->report(new CustomException('Exception message'));
- }
- public function testReturnsJsonWithStackTraceWhenAjaxRequestAndDebugTrue()
- {
- $this->config->shouldReceive('get')->with('app.debug', null)->once()->andReturn(true);
- $this->request->shouldReceive('expectsJson')->once()->andReturn(true);
- $response = $this->handler->render($this->request, new Exception('My custom error message'))->getContent();
- $this->assertStringNotContainsString('<!DOCTYPE html>', $response);
- $this->assertStringContainsString('"message": "My custom error message"', $response);
- $this->assertStringContainsString('"file":', $response);
- $this->assertStringContainsString('"line":', $response);
- $this->assertStringContainsString('"trace":', $response);
- }
- public function testReturnsCustomResponseFromRenderableCallback()
- {
- $this->handler->renderable(function (CustomException $e, $request) {
- $this->assertSame($this->request, $request);
- return response()->json(['response' => 'My custom exception response']);
- });
- $response = $this->handler->render($this->request, new CustomException)->getContent();
- $this->assertSame('{"response":"My custom exception response"}', $response);
- }
- public function testReturnsCustomResponseFromCallableClass()
- {
- $this->handler->renderable(new CustomRenderer);
- $response = $this->handler->render($this->request, new CustomException)->getContent();
- $this->assertSame('{"response":"The CustomRenderer response"}', $response);
- }
- public function testReturnsCustomResponseWhenExceptionImplementsResponsable()
- {
- $response = $this->handler->render($this->request, new ResponsableException)->getContent();
- $this->assertSame('{"response":"My responsable exception response"}', $response);
- }
- public function testReturnsJsonWithoutStackTraceWhenAjaxRequestAndDebugFalseAndExceptionMessageIsMasked()
- {
- $this->config->shouldReceive('get')->with('app.debug', null)->once()->andReturn(false);
- $this->request->shouldReceive('expectsJson')->once()->andReturn(true);
- $response = $this->handler->render($this->request, new Exception('This error message should not be visible'))->getContent();
- $this->assertStringContainsString('"message": "Server Error"', $response);
- $this->assertStringNotContainsString('<!DOCTYPE html>', $response);
- $this->assertStringNotContainsString('This error message should not be visible', $response);
- $this->assertStringNotContainsString('"file":', $response);
- $this->assertStringNotContainsString('"line":', $response);
- $this->assertStringNotContainsString('"trace":', $response);
- }
- public function testReturnsJsonWithoutStackTraceWhenAjaxRequestAndDebugFalseAndHttpExceptionErrorIsShown()
- {
- $this->config->shouldReceive('get')->with('app.debug', null)->once()->andReturn(false);
- $this->request->shouldReceive('expectsJson')->once()->andReturn(true);
- $response = $this->handler->render($this->request, new HttpException(403, 'My custom error message'))->getContent();
- $this->assertStringContainsString('"message": "My custom error message"', $response);
- $this->assertStringNotContainsString('<!DOCTYPE html>', $response);
- $this->assertStringNotContainsString('"message": "Server Error"', $response);
- $this->assertStringNotContainsString('"file":', $response);
- $this->assertStringNotContainsString('"line":', $response);
- $this->assertStringNotContainsString('"trace":', $response);
- }
- public function testReturnsJsonWithoutStackTraceWhenAjaxRequestAndDebugFalseAndAccessDeniedHttpExceptionErrorIsShown()
- {
- $this->config->shouldReceive('get')->with('app.debug', null)->once()->andReturn(false);
- $this->request->shouldReceive('expectsJson')->once()->andReturn(true);
- $response = $this->handler->render($this->request, new AccessDeniedHttpException('My custom error message'))->getContent();
- $this->assertStringContainsString('"message": "My custom error message"', $response);
- $this->assertStringNotContainsString('<!DOCTYPE html>', $response);
- $this->assertStringNotContainsString('"message": "Server Error"', $response);
- $this->assertStringNotContainsString('"file":', $response);
- $this->assertStringNotContainsString('"line":', $response);
- $this->assertStringNotContainsString('"trace":', $response);
- }
- public function testValidateFileMethod()
- {
- $argumentExpected = ['input' => 'My input value'];
- $argumentActual = null;
- $this->container->singleton('redirect', function () use (&$argumentActual) {
- $redirector = m::mock(Redirector::class);
- $redirector->shouldReceive('to')->once()
- ->andReturn($responser = m::mock(RedirectResponse::class));
- $responser->shouldReceive('withInput')->once()->with(m::on(
- function ($argument) use (&$argumentActual) {
- $argumentActual = $argument;
- return true;
- }))->andReturn($responser);
- $responser->shouldReceive('withErrors')->once()
- ->andReturn($responser);
- return $redirector;
- });
- $file = m::mock(UploadedFile::class);
- $file->shouldReceive('getPathname')->andReturn('photo.jpg');
- $file->shouldReceive('getClientOriginalName')->andReturn('photo.jpg');
- $file->shouldReceive('getClientMimeType')->andReturn(null);
- $file->shouldReceive('getError')->andReturn(null);
- $request = Request::create('/', 'POST', $argumentExpected, [], ['photo' => $file]);
- $validator = m::mock(Validator::class);
- $validator->shouldReceive('errors')->andReturn(new MessageBag(['error' => 'My custom validation exception']));
- $validationException = new ValidationException($validator);
- $validationException->redirectTo = '/';
- $this->handler->render($request, $validationException);
- $this->assertEquals($argumentExpected, $argumentActual);
- }
- public function testSuspiciousOperationReturns404WithoutReporting()
- {
- $this->config->shouldReceive('get')->with('app.debug', null)->once()->andReturn(true);
- $this->request->shouldReceive('expectsJson')->once()->andReturn(true);
- $response = $this->handler->render($this->request, new SuspiciousOperationException('Invalid method override "__CONSTRUCT"'));
- $this->assertEquals(404, $response->getStatusCode());
- $this->assertStringContainsString('"message": "Bad hostname provided."', $response->getContent());
- $logger = m::mock(LoggerInterface::class);
- $this->container->instance(LoggerInterface::class, $logger);
- $logger->shouldNotReceive('error');
- $this->handler->report(new SuspiciousOperationException('Invalid method override "__CONSTRUCT"'));
- }
- public function testRecordsNotFoundReturns404WithoutReporting()
- {
- $this->config->shouldReceive('get')->with('app.debug', null)->once()->andReturn(true);
- $this->request->shouldReceive('expectsJson')->once()->andReturn(true);
- $response = $this->handler->render($this->request, new RecordsNotFoundException);
- $this->assertEquals(404, $response->getStatusCode());
- $this->assertStringContainsString('"message": "Not found."', $response->getContent());
- $logger = m::mock(LoggerInterface::class);
- $this->container->instance(LoggerInterface::class, $logger);
- $logger->shouldNotReceive('error');
- $this->handler->report(new RecordsNotFoundException);
- }
- }
- class CustomException extends Exception
- {
- }
- class ResponsableException extends Exception implements Responsable
- {
- public function toResponse($request)
- {
- return response()->json(['response' => 'My responsable exception response']);
- }
- }
- class ReportableException extends Exception
- {
- public function report(ReportingService $reportingService)
- {
- $reportingService->send($this->getMessage());
- }
- }
- class UnReportableException extends Exception
- {
- public function report()
- {
- return false;
- }
- }
- class ContextProvidingException extends Exception
- {
- public function context()
- {
- return [
- 'foo' => 'bar',
- ];
- }
- }
- class CustomReporter
- {
- private $service;
- public function __construct(ReportingService $service)
- {
- $this->service = $service;
- }
- public function __invoke(CustomException $e)
- {
- $this->service->send($e->getMessage());
- return false;
- }
- }
- class CustomRenderer
- {
- public function __invoke(CustomException $e, $request)
- {
- return response()->json(['response' => 'The CustomRenderer response']);
- }
- }
- interface ReportingService
- {
- public function send($message);
- }
|