diff --git a/.travis.yml b/.travis.yml index f24aa134..b404917d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ sudo: false dist: trusty php: - - 5.6 - - 7.0 - 7.1 - 7.2 @@ -13,33 +11,29 @@ cache: - $HOME/.composer/cache/files before_install: + - phpenv config-rm xdebug.ini - composer self-update - composer global require hirak/prestissimo -install: travis_wait travis_retry composer update --no-interaction --prefer-dist --prefer-stable $COMPOSER_OPTIONS +install: + - travis_wait travis_retry composer update --no-interaction --prefer-dist --prefer-stable $COMPOSER_OPTIONS script: - vendor/bin/phpunit -v jobs: include: - - stage: test - php: 5.6 - env: COMPOSER_OPTIONS="--prefer-lowest" - stage: Test - php: 7.0 - env: COMPOSER_OPTIONS="--prefer-lowest" - - stage: codestyle and SCA php: 7.1 + env: COMPOSER_OPTIONS="--prefer-lowest" + - stage: Code style and static analysis script: - - phpenv config-rm xdebug.ini - - composer require --dev phpstan/phpstan symfony/expression-language --no-interaction --prefer-dist --prefer-stable - - vendor/bin/phpstan analyse src --level 7 + - composer phpstan env: PHPSTAN=true - - script: vendor/bin/php-cs-fixer fix --verbose --diff --dry-run + - script: + - composer cs-check env: CS-FIXER=true - stage: coverage - php: 7.1 script: - phpdbg -qrr vendor/bin/phpunit --coverage-clover clover.xml after_success: diff --git a/CHANGELOG.md b/CHANGELOG.md index db301798..b5f3ae00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## 1.0.0 - [Unreleased] +## 2.0.0 - [Unreleased] +### Added + - Add support for Symfony 4.x +### Changed + - The `SentryBundle::VERSION` constant has been replaced with the `SentryBundle::getVersion(): string` method, to get a more accurate result + - Due to a deprecation in `symfony/console`, we require it at at least version 3.3, and we added a method to `SentryExceptionListenerInterface`: +```php + public function onConsoleException(ConsoleErrorEvent $event); +``` +### Removed + - Drop support for Symfony 2.x + - Drop support for PHP 5 and 7.0 + +## 1.0.0 - 2017-11-07 ### Added - Add official support to PHP 7.2 (#71) ### Changed diff --git a/composer.json b/composer.json index 8f1579ae..ff1efce6 100644 --- a/composer.json +++ b/composer.json @@ -19,20 +19,24 @@ "sort-packages": true }, "require": { - "php": "^5.6|^7.0", + "php": "^7.1", + "jean85/pretty-package-versions": "^1.0", "sentry/sentry": "^1.8", - "symfony/config": "^2.7|^3.0", - "symfony/console": "^2.7|^3.0", - "symfony/dependency-injection": "^2.7|^3.0", - "symfony/event-dispatcher": "^2.7|^3.0", - "symfony/http-kernel": "^2.7|^3.0", - "symfony/security-core": "^2.7|^3.0", - "symfony/yaml": "^2.7|^3.0" + "symfony/config": "^3.0||^4.0", + "symfony/console": "^3.3||^4.0", + "symfony/dependency-injection": "^3.0||^4.0", + "symfony/event-dispatcher": "^3.0||^4.0", + "symfony/http-kernel": "^3.0||^4.0", + "symfony/security-core": "^3.0||^4.0", + "symfony/yaml": "^3.0||^4.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.5", - "phpunit/phpunit": "^5.7|^6.0", - "scrutinizer/ocular": "^1.4" + "friendsofphp/php-cs-fixer": "^2.8", + "phpstan/phpstan": "^0.9.1", + "phpstan/phpstan-phpunit": "^0.9.1", + "phpunit/phpunit": "^6.5", + "scrutinizer/ocular": "^1.4", + "symfony/expression-language": "^3.0||^4.0" }, "autoload": { "psr-4" : { @@ -44,9 +48,16 @@ "Sentry\\SentryBundle\\Test\\": "test" } }, + "scripts": { + "phpstan": "vendor/bin/phpstan analyse src test --level 7 -c phpstan.neon", + "cs-check": "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run", + "cs-fix": "vendor/bin/php-cs-fixer fix --verbose --diff" + }, "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "master": "2.0.x-dev", + "releases/0.8.x": "0.8.x-dev", + "releases/1.x": "1.0.x-dev" } } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..94862647 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - '/Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface::/' + - '/Calling method \w+ on possibly null value of type Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface|null/' +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 49a5d3cd..662534fb 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -6,6 +6,7 @@ use Sentry\SentryBundle\EventListener\ExceptionListener; use Sentry\SentryBundle\EventListener\SentryExceptionListenerInterface; use Sentry\SentryBundle\SentrySymfonyClient; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; @@ -25,6 +26,7 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); + /** @var ArrayNodeDefinition $rootNode */ $rootNode = $treeBuilder->root('sentry'); // Basic Sentry configuration @@ -138,10 +140,7 @@ public function getConfigTreeBuilder() return $treeBuilder; } - /** - * @return \Closure - */ - private function getExceptionListenerInvalidationClosure() + private function getExceptionListenerInvalidationClosure(): callable { return function ($value) { $implements = class_implements($value); @@ -153,10 +152,7 @@ private function getExceptionListenerInvalidationClosure() }; } - /** - * @return \Closure - */ - private function getTrimClosure() + private function getTrimClosure(): callable { return function ($str) { $value = trim($str); diff --git a/src/ErrorTypesParser.php b/src/ErrorTypesParser.php index fa037352..a5c21950 100644 --- a/src/ErrorTypesParser.php +++ b/src/ErrorTypesParser.php @@ -7,14 +7,15 @@ */ class ErrorTypesParser { - private $expression = null; + /** @var string */ + private $expression; /** * Initialize ErrorParser * * @param string $expression Error Types e.g. E_ALL & ~E_DEPRECATED & ~E_NOTICE */ - public function __construct($expression) + public function __construct(string $expression) { $this->expression = $expression; } @@ -23,8 +24,9 @@ public function __construct($expression) * Parse and compute the error types expression * * @return int the parsed expression + * @throws \InvalidArgumentException */ - public function parse() + public function parse(): int { // convert constants to ints $this->expression = $this->convertErrorConstants($this->expression); @@ -43,7 +45,7 @@ public function parse() * @param string $expression e.g. E_ALL & ~E_DEPRECATED & ~E_NOTICE * @return string converted expression e.g. 32767 & ~8192 & ~8 */ - private function convertErrorConstants($expression) + private function convertErrorConstants(string $expression): string { $output = preg_replace_callback('/(E_[a-zA-Z_]+)/', function ($errorConstant) { if (defined($errorConstant[1])) { @@ -61,8 +63,9 @@ private function convertErrorConstants($expression) * * @param string $expression prepared expression e.g. 32767&~8192&~8 * @return int computed expression e.g. 24567 + * @throws \InvalidArgumentException */ - private function compute($expression) + private function compute(string $expression): int { // catch anything which could be a security issue if (0 !== preg_match("/[^\d.+*%^|&~<>\/()-]/", $this->expression)) { diff --git a/src/Event/SentryUserContextEvent.php b/src/Event/SentryUserContextEvent.php index ff9fd255..5369908b 100644 --- a/src/Event/SentryUserContextEvent.php +++ b/src/Event/SentryUserContextEvent.php @@ -7,6 +7,7 @@ class SentryUserContextEvent extends Event { + /** @var TokenInterface */ private $authenticationToken; public function __construct(TokenInterface $authenticationToken) @@ -14,7 +15,7 @@ public function __construct(TokenInterface $authenticationToken) $this->authenticationToken = $authenticationToken; } - public function getAuthenticationToken() + public function getAuthenticationToken(): TokenInterface { return $this->authenticationToken; } diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php index 5b878f08..ed239291 100644 --- a/src/EventListener/ExceptionListener.php +++ b/src/EventListener/ExceptionListener.php @@ -5,6 +5,8 @@ use Sentry\SentryBundle\Event\SentryUserContextEvent; use Sentry\SentryBundle\SentrySymfonyEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -71,7 +73,7 @@ public function setClient(\Raven_Client $client) * * @param GetResponseEvent $event */ - public function onKernelRequest(GetResponseEvent $event) + public function onKernelRequest(GetResponseEvent $event): void { if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { return; @@ -94,7 +96,7 @@ public function onKernelRequest(GetResponseEvent $event) /** * @param GetResponseForExceptionEvent $event */ - public function onKernelException(GetResponseForExceptionEvent $event) + public function onKernelException(GetResponseForExceptionEvent $event): void { $exception = $event->getException(); @@ -114,18 +116,37 @@ public function onKernelException(GetResponseForExceptionEvent $event) * * @return void */ - public function onConsoleCommand(ConsoleCommandEvent $event) + public function onConsoleCommand(ConsoleCommandEvent $event): void { // only triggers loading of client, does not need to do anything. } + public function onConsoleError(ConsoleErrorEvent $event): void + { + $this->handleConsoleError($event); + } + + public function onConsoleException(ConsoleExceptionEvent $event): void + { + $this->handleConsoleError($event); + } + /** - * @param ConsoleExceptionEvent $event + * @param ConsoleExceptionEvent|ConsoleErrorEvent $event */ - public function onConsoleException(ConsoleExceptionEvent $event) + private function handleConsoleError(ConsoleEvent $event): void { $command = $event->getCommand(); - $exception = $event->getException(); + switch (true) { + case $event instanceof ConsoleErrorEvent: + $exception = $event->getError(); + break; + case $event instanceof ConsoleExceptionEvent: + $exception = $event->getException(); + break; + default: + throw new \InvalidArgumentException('Event not recognized: ' . \get_class($event)); + } if ($this->shouldExceptionCaptureBeSkipped($exception)) { return; @@ -142,7 +163,7 @@ public function onConsoleException(ConsoleExceptionEvent $event) $this->client->captureException($exception, $data); } - protected function shouldExceptionCaptureBeSkipped(\Exception $exception) + protected function shouldExceptionCaptureBeSkipped(\Throwable $exception): bool { foreach ($this->skipCapture as $className) { if ($exception instanceof $className) { @@ -171,7 +192,7 @@ private function setUserValue($user) } if (is_object($user) && method_exists($user, '__toString')) { - $this->client->set_user_data($user->__toString()); + $this->client->set_user_data((string)$user); } } } diff --git a/src/EventListener/SentryExceptionListenerInterface.php b/src/EventListener/SentryExceptionListenerInterface.php index 7f91f526..9d972074 100644 --- a/src/EventListener/SentryExceptionListenerInterface.php +++ b/src/EventListener/SentryExceptionListenerInterface.php @@ -2,6 +2,7 @@ namespace Sentry\SentryBundle\EventListener; +use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; @@ -17,7 +18,7 @@ interface SentryExceptionListenerInterface * * @param GetResponseEvent $event */ - public function onKernelRequest(GetResponseEvent $event); + public function onKernelRequest(GetResponseEvent $event): void; /** * When an exception occurs as part of a web request, this method will be @@ -25,13 +26,22 @@ public function onKernelRequest(GetResponseEvent $event); * * @param GetResponseForExceptionEvent $event */ - public function onKernelException(GetResponseForExceptionEvent $event); + public function onKernelException(GetResponseForExceptionEvent $event): void; + + /** + * When an exception occurs on the command line, this method will be + * triggered for capturing the error. + * + * @param ConsoleErrorEvent $event + */ + public function onConsoleError(ConsoleErrorEvent $event): void; /** * When an exception occurs on the command line, this method will be * triggered for capturing the error. * * @param ConsoleExceptionEvent $event + * @deprecated This method exists for BC with Symfony 3.x */ - public function onConsoleException(ConsoleExceptionEvent $event); + public function onConsoleException(ConsoleExceptionEvent $event): void; } diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 213c8be5..018b0612 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -1,20 +1,22 @@ services: - sentry.client: - class: '%sentry.client%' - arguments: ['%sentry.dsn%', '%sentry.options%'] - calls: - - [install, []] + sentry.client: + class: '%sentry.client%' + arguments: ['%sentry.dsn%', '%sentry.options%'] + public: true + calls: + - [install, []] - sentry.exception_listener: - class: '%sentry.exception_listener%' - arguments: - - '@sentry.client' - - '@event_dispatcher' - - '%sentry.skip_capture%' - - '@?security.token_storage' - - '@?security.authorization_checker' - tags: - - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%sentry.listener_priorities.request%'} - - { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: '%sentry.listener_priorities.kernel_exception%' } - - { name: kernel.event_listener, event: console.command, method: onConsoleCommand } - - { name: kernel.event_listener, event: console.exception, method: onConsoleException, priority: '%sentry.listener_priorities.console_exception%' } + sentry.exception_listener: + class: '%sentry.exception_listener%' + arguments: + - '@sentry.client' + - '@event_dispatcher' + - '%sentry.skip_capture%' + - '@?security.token_storage' + - '@?security.authorization_checker' + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%sentry.listener_priorities.request%'} + - { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: '%sentry.listener_priorities.kernel_exception%' } + - { name: kernel.event_listener, event: console.command, method: onConsoleCommand } + - { name: kernel.event_listener, event: console.exception, method: onConsoleException, priority: '%sentry.listener_priorities.console_exception%' } + - { name: kernel.event_listener, event: console.error, method: onConsoleError, priority: '%sentry.listener_priorities.console_exception%' } diff --git a/src/SentryBundle.php b/src/SentryBundle.php index 3403d1bc..fa563a96 100644 --- a/src/SentryBundle.php +++ b/src/SentryBundle.php @@ -2,9 +2,14 @@ namespace Sentry\SentryBundle; +use Jean85\PrettyVersions; use Symfony\Component\HttpKernel\Bundle\Bundle; class SentryBundle extends Bundle { - const VERSION = '1.0-dev'; + public static function getVersion(): string + { + return PrettyVersions::getVersion('sentry/sentry-symfony') + ->getPrettyVersion(); + } } diff --git a/src/SentrySymfonyClient.php b/src/SentrySymfonyClient.php index 723d6c37..69e33531 100644 --- a/src/SentrySymfonyClient.php +++ b/src/SentrySymfonyClient.php @@ -4,7 +4,7 @@ class SentrySymfonyClient extends \Raven_Client { - public function __construct($dsn = null, $options = []) + public function __construct(?string $dsn = null, array $options = []) { if (! empty($options['error_types'])) { $exParser = new ErrorTypesParser($options['error_types']); @@ -13,7 +13,7 @@ public function __construct($dsn = null, $options = []) $options['sdk'] = [ 'name' => 'sentry-symfony', - 'version' => SentryBundle::VERSION, + 'version' => SentryBundle::getVersion(), ]; parent::__construct($dsn, $options); diff --git a/test/DependencyInjection/SentryExtensionTest.php b/test/DependencyInjection/SentryExtensionTest.php index 73482c6e..f5e1c6bb 100644 --- a/test/DependencyInjection/SentryExtensionTest.php +++ b/test/DependencyInjection/SentryExtensionTest.php @@ -8,13 +8,16 @@ use Sentry\SentryBundle\SentrySymfonyClient; use Sentry\SentryBundle\Test\Fixtures\CustomExceptionListener; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; class SentryExtensionTest extends TestCase { - const SUPPORTED_SENTRY_OPTIONS_COUNT = 34; + private const SUPPORTED_SENTRY_OPTIONS_COUNT = 34; + private const LISTENER_TEST_PUBLIC_ALIAS = 'sentry.exception_listener.public_alias'; public function test_that_configuration_uses_the_right_default_values() { @@ -258,8 +261,8 @@ public function test_that_it_has_sentry_client_service_and_it_defaults_to_symfon public function test_that_it_has_sentry_exception_listener_and_it_defaults_to_default_exception_listener() { - $client = $this->getContainer()->get('sentry.exception_listener'); - $this->assertInstanceOf(ExceptionListener::class, $client); + $listener = $this->getContainer()->get(self::LISTENER_TEST_PUBLIC_ALIAS); + $this->assertInstanceOf(ExceptionListener::class, $listener); } public function test_that_it_has_proper_event_listener_tags_for_exception_listener() @@ -289,6 +292,11 @@ public function test_that_it_has_proper_event_listener_tags_for_exception_listen 'method' => 'onConsoleException', 'priority' => '%sentry.listener_priorities.console_exception%', ], + [ + 'event' => 'console.error', + 'method' => 'onConsoleError', + 'priority' => '%sentry.listener_priorities.console_exception%', + ], ], $tags ); @@ -347,7 +355,11 @@ public function test_that_it_sets_all_sentry_options() $this->assertCount(self::SUPPORTED_SENTRY_OPTIONS_COUNT, $options); $defaultOptions = $this->getContainer()->getParameter('sentry.options'); foreach ($options as $name => $value) { - $this->assertNotEquals($defaultOptions[$name], $value, 'Test precondition failed: using default value for ' . $name); + $this->assertNotEquals( + $defaultOptions[$name], + $value, + 'Test precondition failed: using default value for ' . $name + ); } $container = $this->getContainer(['options' => $options]); @@ -355,11 +367,7 @@ public function test_that_it_sets_all_sentry_options() $this->assertSame($options, $container->getParameter('sentry.options')); } - /** - * @param array $configuration - * @return ContainerBuilder - */ - private function getContainer(array $configuration = []) + private function getContainer(array $configuration = []): Container { $containerBuilder = new ContainerBuilder(); $containerBuilder->setParameter('kernel.root_dir', 'kernel/root'); @@ -369,6 +377,7 @@ private function getContainer(array $configuration = []) ->createMock(EventDispatcherInterface::class); $containerBuilder->set('event_dispatcher', $mockEventDispatcher); + $containerBuilder->setAlias(self::LISTENER_TEST_PUBLIC_ALIAS, new Alias('sentry.exception_listener', true)); $extension = new SentryExtension(); $extension->load(['sentry' => $configuration], $containerBuilder); diff --git a/test/EventListener/ExceptionListenerTest.php b/test/EventListener/ExceptionListenerTest.php index 9c18e52b..b3cf8029 100644 --- a/test/EventListener/ExceptionListenerTest.php +++ b/test/EventListener/ExceptionListenerTest.php @@ -6,9 +6,14 @@ use Sentry\SentryBundle\DependencyInjection\SentryExtension; use Sentry\SentryBundle\Event\SentryUserContextEvent; use Sentry\SentryBundle\EventListener\ExceptionListener; +use Sentry\SentryBundle\EventListener\SentryExceptionListenerInterface; use Sentry\SentryBundle\SentrySymfonyEvents; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -23,19 +28,16 @@ class ExceptionListenerTest extends TestCase { - /** @var ContainerBuilder|\PHPUnit_Framework_MockObject_MockObject */ + private const LISTENER_TEST_PUBLIC_ALIAS = 'sentry.exception_listener.public_alias'; + private $containerBuilder; - /** @var \Raven_Client|\PHPUnit_Framework_MockObject_MockObject */ private $mockSentryClient; - /** @var TokenStorageInterface|\PHPUnit_Framework_MockObject_MockObject */ private $mockTokenStorage; - /** @var AuthorizationCheckerInterface|\PHPUnit_Framework_MockObject_MockObject */ private $mockAuthorizationChecker; - /** @var EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject */ private $mockEventDispatcher; public function setUp() @@ -53,6 +55,7 @@ public function setUp() $containerBuilder->set('security.authorization_checker', $this->mockAuthorizationChecker); $containerBuilder->set('sentry.client', $this->mockSentryClient); $containerBuilder->set('event_dispatcher', $this->mockEventDispatcher); + $containerBuilder->setAlias(self::LISTENER_TEST_PUBLIC_ALIAS, new Alias('sentry.exception_listener', true)); $extension = new SentryExtension(); $extension->load([], $containerBuilder); @@ -63,7 +66,7 @@ public function setUp() public function test_that_it_is_an_instance_of_sentry_exception_listener() { $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $this->assertInstanceOf(ExceptionListener::class, $listener); } @@ -75,23 +78,20 @@ public function test_that_user_data_is_not_set_on_subrequest() $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::SUB_REQUEST) - ; + ->willReturn(HttpKernelInterface::SUB_REQUEST); $this->mockSentryClient ->expects($this->never()) ->method('set_user_data') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->mockEventDispatcher ->expects($this->never()) ->method('dispatch') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -104,26 +104,23 @@ public function test_that_user_data_is_not_set_if_token_storage_not_present() $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this->mockSentryClient ->expects($this->never()) ->method('set_user_data') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->mockEventDispatcher ->expects($this->never()) ->method('dispatch') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->assertFalse($this->containerBuilder->has('security.token_storage')); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -136,26 +133,23 @@ public function test_that_user_data_is_not_set_if_authorization_checker_not_pres $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this->mockSentryClient ->expects($this->never()) ->method('set_user_data') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->mockEventDispatcher ->expects($this->never()) ->method('dispatch') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->containerBuilder->compile(); $this->assertFalse($this->containerBuilder->has('security.authorization_checker')); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -170,34 +164,29 @@ public function test_that_user_data_is_not_set_if_token_not_present() $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this->mockAuthorizationChecker ->method('isGranted') ->with($this->identicalTo(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED)) - ->willReturn(true) - ; + ->willReturn(true); $this->mockTokenStorage ->method('getToken') - ->willReturn(null) - ; + ->willReturn(null); $this->mockSentryClient ->expects($this->never()) ->method('set_user_data') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->mockEventDispatcher ->expects($this->never()) ->method('dispatch') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -210,41 +199,35 @@ public function test_that_user_data_is_not_set_if_not_authorized() $mockToken ->method('getUser') - ->willReturn($user) - ; + ->willReturn($user); $mockEvent = $this->createMock(GetResponseEvent::class); $mockEvent->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this->mockAuthorizationChecker ->method('isGranted') ->with($this->identicalTo(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED)) - ->willReturn(false) - ; + ->willReturn(false); $this->mockTokenStorage ->method('getToken') - ->willReturn($mockToken) - ; + ->willReturn($mockToken); $this->mockSentryClient ->expects($this->never()) ->method('set_user_data') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->mockEventDispatcher ->expects($this->never()) ->method('dispatch') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -257,40 +240,34 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a $mockToken ->method('getUser') - ->willReturn($user) - ; + ->willReturn($user); $mockToken ->method('isAuthenticated') - ->willReturn(true) - ; + ->willReturn(true); $mockEvent = $this->createMock(GetResponseEvent::class); $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this ->mockAuthorizationChecker ->method('isGranted') ->with($this->identicalTo(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED)) - ->willReturn(true) - ; + ->willReturn(true); $this->mockTokenStorage ->method('getToken') - ->willReturn($mockToken) - ; + ->willReturn($mockToken); $this ->mockSentryClient ->expects($this->once()) ->method('set_user_data') - ->with($this->identicalTo('username')) - ; + ->with($this->identicalTo('username')); $this ->mockEventDispatcher @@ -299,11 +276,10 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a ->with( $this->identicalTo(SentrySymfonyEvents::SET_USER_CONTEXT), $this->isInstanceOf(SentryUserContextEvent::class) - ) - ; + ); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -313,38 +289,32 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a $mockToken ->method('getUser') - ->willReturn('some_user') - ; + ->willReturn('some_user'); $mockToken ->method('isAuthenticated') - ->willReturn(true) - ; + ->willReturn(true); $mockEvent = $this->createMock(GetResponseEvent::class); $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this->mockAuthorizationChecker ->method('isGranted') ->with($this->identicalTo(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED)) - ->willReturn(true) - ; + ->willReturn(true); $this->mockTokenStorage ->method('getToken') - ->willReturn($mockToken) - ; + ->willReturn($mockToken); $this->mockSentryClient ->expects($this->once()) ->method('set_user_data') - ->with($this->identicalTo('some_user')) - ; + ->with($this->identicalTo('some_user')); $this->mockEventDispatcher ->expects($this->once()) @@ -352,11 +322,10 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a ->with( $this->identicalTo(SentrySymfonyEvents::SET_USER_CONTEXT), $this->isInstanceOf(SentryUserContextEvent::class) - ) - ; + ); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -364,51 +333,43 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a { $mockUser = $this->getMockBuilder('stdClass') ->setMethods(['__toString']) - ->getMock() - ; + ->getMock(); $mockUser ->expects($this->once()) ->method('__toString') - ->willReturn('std_user') - ; + ->willReturn('std_user'); $mockToken = $this->createMock(TokenInterface::class); $mockToken ->method('getUser') - ->willReturn($mockUser) - ; + ->willReturn($mockUser); $mockToken ->method('isAuthenticated') - ->willReturn(true) - ; + ->willReturn(true); $mockEvent = $this->createMock(GetResponseEvent::class); $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this->mockAuthorizationChecker ->method('isGranted') ->with($this->identicalTo(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED)) - ->willReturn(true) - ; + ->willReturn(true); $this->mockTokenStorage ->method('getToken') - ->willReturn($mockToken) - ; + ->willReturn($mockToken); $this->mockSentryClient ->expects($this->once()) ->method('set_user_data') - ->with($this->identicalTo('std_user')) - ; + ->with($this->identicalTo('std_user')); $this->mockEventDispatcher ->expects($this->once()) @@ -416,11 +377,10 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a ->with( $this->identicalTo(SentrySymfonyEvents::SET_USER_CONTEXT), $this->isInstanceOf(SentryUserContextEvent::class) - ) - ; + ); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -429,24 +389,21 @@ public function test_regression_with_unauthenticated_user_token_PR_78() $mockToken = $this->createMock(TokenInterface::class); $mockToken ->method('isAuthenticated') - ->willReturn(false) - ; + ->willReturn(false); $mockEvent = $this->createMock(GetResponseEvent::class); $mockEvent ->expects($this->once()) ->method('getRequestType') - ->willReturn(HttpKernelInterface::MASTER_REQUEST) - ; + ->willReturn(HttpKernelInterface::MASTER_REQUEST); $this->mockTokenStorage ->method('getToken') - ->willReturn($mockToken) - ; + ->willReturn($mockToken); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelRequest($mockEvent); } @@ -457,23 +414,20 @@ public function test_that_it_does_not_report_http_exception_if_included_in_captu $mockEvent ->expects($this->once()) ->method('getException') - ->willReturn(new HttpException(401)) - ; + ->willReturn(new HttpException(401)); $this->mockEventDispatcher ->expects($this->never()) ->method('dispatch') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->mockSentryClient ->expects($this->never()) ->method('captureException') - ->withAnyParameters() - ; + ->withAnyParameters(); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelException($mockEvent); } @@ -485,76 +439,100 @@ public function test_that_it_captures_exception() $mockEvent ->expects($this->once()) ->method('getException') - ->willReturn($reportableException) - ; + ->willReturn($reportableException); $this->mockEventDispatcher ->expects($this->once()) ->method('dispatch') - ->with($this->identicalTo(SentrySymfonyEvents::PRE_CAPTURE), $this->identicalTo($mockEvent)) - ; + ->with($this->identicalTo(SentrySymfonyEvents::PRE_CAPTURE), $this->identicalTo($mockEvent)); $this->mockSentryClient ->expects($this->once()) ->method('captureException') - ->with($this->identicalTo($reportableException)) - ; + ->with($this->identicalTo($reportableException)); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + $listener = $this->getListener(); $listener->onKernelException($mockEvent); } /** * @dataProvider mockCommandProvider */ - public function test_that_it_captures_console_exception(Command $mockCommand = null, $expectedCommandName) + public function test_that_it_captures_console_exception(?Command $mockCommand, string $expectedCommandName) { - $reportableException = new \Exception(); + if (null === $mockCommand) { + $this->markTestSkipped('Command missing is not possibile with ConsoleExceptionEvent'); + } - $mockEvent = $this->createMock(ConsoleExceptionEvent::class); + $exception = $this->createMock(\Exception::class); + /** @var InputInterface $input */ + $input = $this->createMock(InputInterface::class); + /** @var OutputInterface $output */ + $output = $this->createMock(OutputInterface::class); - $mockEvent - ->expects($this->once()) - ->method('getExitCode') - ->willReturn(10) - ; + $event = new ConsoleExceptionEvent($mockCommand, $input, $output, $exception, 10); - $mockEvent + $this->mockEventDispatcher ->expects($this->once()) - ->method('getException') - ->willReturn($reportableException) - ; + ->method('dispatch') + ->with($this->identicalTo(SentrySymfonyEvents::PRE_CAPTURE), $this->identicalTo($event)); - $mockEvent + $this->mockSentryClient ->expects($this->once()) - ->method('getCommand') - ->willReturn($mockCommand) - ; + ->method('captureException') + ->with( + $this->identicalTo($exception), + $this->identicalTo([ + 'tags' => [ + 'command' => $expectedCommandName, + 'status_code' => 10, + ], + ]) + ); + + $this->containerBuilder->compile(); + /** @var SentryExceptionListenerInterface $listener */ + $listener = $this->getListener(); + $listener->onConsoleException($event); + } + + /** + * @dataProvider mockCommandProvider + */ + public function test_that_it_captures_console_error(?Command $mockCommand, string $expectedCommandName) + { + $error = $this->createMock(\Error::class); + /** @var InputInterface $input */ + $input = $this->createMock(InputInterface::class); + /** @var OutputInterface $output */ + $output = $this->createMock(OutputInterface::class); + + $event = new ConsoleErrorEvent($input, $output, $error, $mockCommand); + $event->setExitCode(10); $this->mockEventDispatcher ->expects($this->once()) ->method('dispatch') - ->with($this->identicalTo(SentrySymfonyEvents::PRE_CAPTURE), $this->identicalTo($mockEvent)) - ; + ->with($this->identicalTo(SentrySymfonyEvents::PRE_CAPTURE), $this->identicalTo($event)); $this->mockSentryClient ->expects($this->once()) ->method('captureException') ->with( - $this->identicalTo($reportableException), + $this->identicalTo($error), $this->identicalTo([ 'tags' => [ 'command' => $expectedCommandName, 'status_code' => 10, ], ]) - ) - ; + ); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); - $listener->onConsoleException($mockEvent); + /** @var SentryExceptionListenerInterface $listener */ + $listener = $this->getListener(); + $listener->onConsoleError($event); } public function mockCommandProvider() @@ -563,8 +541,7 @@ public function mockCommandProvider() $mockCommand ->expects($this->once()) ->method('getName') - ->willReturn('cmd name') - ; + ->willReturn('cmd name'); return [ [$mockCommand, 'cmd name'], @@ -574,7 +551,7 @@ public function mockCommandProvider() public function test_that_it_can_replace_client() { - $replacementClient = $this->createMock('Raven_Client'); + $replacementClient = $this->createMock(\Raven_Client::class); $reportableException = new \Exception(); @@ -583,32 +560,35 @@ public function test_that_it_can_replace_client() $mockEvent ->expects($this->once()) ->method('getException') - ->willReturn($reportableException) - ; + ->willReturn($reportableException); $this->mockEventDispatcher ->expects($this->once()) ->method('dispatch') - ->with($this->identicalTo(SentrySymfonyEvents::PRE_CAPTURE), $this->identicalTo($mockEvent)) - ; + ->with($this->identicalTo(SentrySymfonyEvents::PRE_CAPTURE), $this->identicalTo($mockEvent)); $this->mockSentryClient ->expects($this->never()) ->method('captureException') - ->withAnyParameters() - ; + ->withAnyParameters(); $replacementClient ->expects($this->once()) ->method('captureException') - ->with($this->identicalTo($reportableException)) - ; + ->with($this->identicalTo($reportableException)); $this->containerBuilder->compile(); - $listener = $this->containerBuilder->get('sentry.exception_listener'); + /** @var ExceptionListener $listener */ + $listener = $this->getListener(); + $this->assertInstanceOf(ExceptionListener::class, $listener); $listener->setClient($replacementClient); $listener->onKernelException($mockEvent); } + + private function getListener(): SentryExceptionListenerInterface + { + return $this->containerBuilder->get(self::LISTENER_TEST_PUBLIC_ALIAS); + } } diff --git a/test/SentrySymfonyClientTest.php b/test/SentrySymfonyClientTest.php index 8f82fa80..af7c52a7 100644 --- a/test/SentrySymfonyClientTest.php +++ b/test/SentrySymfonyClientTest.php @@ -15,7 +15,7 @@ public function test_that_it_sets_sdk_name_and_version() $data = $client->get_default_data(); $this->assertEquals('sentry-symfony', $data['sdk']['name']); - $this->assertEquals(SentryBundle::VERSION, $data['sdk']['version']); + $this->assertEquals(SentryBundle::getVersion(), $data['sdk']['version']); } public function test_that_it_forwards_options()