diff --git a/composer.json b/composer.json index 9dd0f5a69f..7660b4e276 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,9 @@ "symfony/web-link": "^5.4|^6.0|^7.0|^8.0", "vincentlanglet/twig-cs-fixer": "^3.10" }, + "conflict": { + "symfony/error-handler": "<5.4.35" + }, "config": { "sort-packages": true, "allow-plugins": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 913281fd33..30da8a1348 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -19,7 +19,6 @@ tests/ tests/Controller/PrettyUrls/ - tests/EventListener/ tests/Form/ diff --git a/tests/EventListener/ExceptionListenerTest.php b/tests/EventListener/ExceptionListenerTest.php index bd519fb758..d87dac48a1 100644 --- a/tests/EventListener/ExceptionListenerTest.php +++ b/tests/EventListener/ExceptionListenerTest.php @@ -2,81 +2,120 @@ namespace EasyCorp\Bundle\EasyAdminBundle\Tests\EventListener; +use EasyCorp\Bundle\EasyAdminBundle\Context\ExceptionContext; +use EasyCorp\Bundle\EasyAdminBundle\Contracts\Context\AdminContextInterface; +use EasyCorp\Bundle\EasyAdminBundle\Contracts\Provider\AdminContextProviderInterface; use EasyCorp\Bundle\EasyAdminBundle\EventListener\ExceptionListener; -use EasyCorp\Bundle\EasyAdminBundle\Exception\EntityNotFoundException as EasyEntityNotFoundException; +use EasyCorp\Bundle\EasyAdminBundle\Exception\BaseException; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Twig\Environment; +use Twig\Error\RuntimeError; +use Twig\Loader\ArrayLoader; class ExceptionListenerTest extends TestCase { - private function getTwig() + /** + * @dataProvider unhandledException + */ + public function testUnhandledException(bool $kernelDebug, bool $contextIsNull, \Exception $exception): void { - $twig = $this->getMockBuilder('\Twig_Environment')->disableOriginalConstructor()->getMock(); - $twig->method('render')->willReturn('template content'); + $contextProvider = $this->createMock(AdminContextProviderInterface::class); + if (!$contextIsNull) { + $context = $this->createMock(AdminContextInterface::class); + $context->method('getTemplatePath')->willReturn('foo'); + $contextProvider->method('getContext')->willReturn($context); + } - return $twig; - } + $listener = new ExceptionListener( + $kernelDebug, + $contextProvider, + $this->createMock(Environment::class), + ); - private function getEventExceptionThatShouldBeCalledOnce($exception) - { - $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent') - ->disableOriginalConstructor() - ->getMock(); - $event->method('getException')->willReturn($exception); - $event->method('getRequest')->willReturn(new Request()); - $event->method('getKernel')->willReturn(new TestKernel()); - $event->expects($this->once())->method('setResponse'); - - return $event; + $expectedMessage = $exception->getMessage(); + + $listener->onKernelException($exceptionEvent = $this->createExceptionEvent($exception)); + + $this->assertSame($expectedMessage, $exceptionEvent->getThrowable()->getMessage()); + $this->assertNull($exceptionEvent->getResponse()); } - public function testCatchBaseExceptions(): void + public static function unhandledException(): \Generator { - $exception = new EasyEntityNotFoundException([ - 'entity_name' => 'Test', - 'entity_id_name' => 'Test key', - 'entity_id_value' => 2, - ]); - $event = $this->getEventExceptionThatShouldBeCalledOnce($exception); - $twig = $this->getTwig(); - - $listener = new ExceptionListener($twig, []); - $listener->onKernelException($event); + yield [true, true, new \Exception()]; + yield [true, false, new \Exception()]; + yield [false, true, new \Exception()]; + yield [false, false, new \Exception()]; + yield [true, true, new RuntimeError('foo')]; + yield [true, false, new RuntimeError('foo')]; + yield [false, true, new RuntimeError('foo')]; + yield [false, false, new RuntimeError('foo')]; + yield [true, true, new class(new ExceptionContext('foo')) extends BaseException {}]; + yield [true, false, new class(new ExceptionContext('foo')) extends BaseException {}]; + yield [false, true, new class(new ExceptionContext('foo')) extends BaseException {}]; } - private function getEventExceptionThatShouldNotBeCalled($exception) + public function testAppendMessage(): void { - $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent') - ->disableOriginalConstructor() - ->getMock(); - $event->method('getException')->willReturn($exception); - $event->method('getRequest')->willReturn(new Request()); - $event->expects($this->never())->method('setResponse'); - - return $event; + $listener = new ExceptionListener( + true, + $this->createMock(AdminContextProviderInterface::class), + $this->createMock(Environment::class), + ); + + $exception = new RuntimeError('Variable "ea" does not exist.'); + $listener->onKernelException($exceptionEvent = $this->createExceptionEvent($exception)); + + $expectedMessage = <<assertSame($expectedMessage, $exceptionEvent->getThrowable()->getMessage()); + $this->assertNull($exceptionEvent->getResponse()); } - public function testShouldNotCatchExceptionsWithSameName(): void + public function testResponse(): void { - $exception = new EntityNotFoundException(); - $event = $this->getEventExceptionThatShouldNotBeCalled($exception); - $twig = $this->getTwig(); + $contextProvider = $this->createMock(AdminContextProviderInterface::class); + $context = $this->createMock(AdminContextInterface::class); + $context->method('getTemplatePath')->willReturn('@EasyAdmin/exception.html.twig'); + $contextProvider->method('getContext')->willReturn($context); + $listener = new ExceptionListener( + false, + $contextProvider, + new Environment(new ArrayLoader(['@EasyAdmin/exception.html.twig' => '{{ exception.publicMessage }}'])), + ); - $listener = new ExceptionListener($twig, []); - $listener->onKernelException($event); - } -} + $exception = new class(new ExceptionContext('foo')) extends BaseException {}; -class EntityNotFoundException extends \Exception -{ -} + $expectedStatusCode = $exception->getStatusCode(); -class TestKernel implements HttpKernelInterface -{ - public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true): Response + $listener->onKernelException($exceptionEvent = $this->createExceptionEvent($exception)); + + $this->assertSame($expectedStatusCode, $exceptionEvent->getResponse()->getStatusCode()); + $this->assertSame('foo', $exceptionEvent->getResponse()->getContent()); + } + + private function createExceptionEvent(\Exception $exception): ExceptionEvent { - return new Response('foo'); + return new ExceptionEvent( + $this->createStub(HttpKernelInterface::class), + Request::create('/'), + HttpKernelInterface::MAIN_REQUEST, + $exception, + ); } }