diff --git a/.travis.yml b/.travis.yml index 78ae2c0..03dad71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,7 @@ before_script: fi script: - - php ./vendor/bin/phpcs --standard=vendor/mito/yii2-coding-standards/Application src - - php ./vendor/bin/phpcs --standard=vendor/mito/yii2-coding-standards/Application -s --exclude=PSR1.Files.SideEffects,PSR1.Classes.ClassDeclaration --extensions=php tests + - sh ./phpcs.sh - php ./vendor/bin/codecept run unit -d $PHPUNIT_FLAGS after_success: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3e12d15 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +2016-11-23 (aborsos) - 1.0.0. + - validate sentry DSN even if the component is disabled + - DSN is required even if the component is disabled + - default environment tag is `production` + - remove deprecated methods and properties + - separate init method's content to individual methods + - renamed `\mito\sentry\SentryComponent` to `\mito\sentry\Component` and `\mito\sentry\SentryTarget` to `\mito\sentry\Target` + - catch array type exceptions and show it nicely in Sentry (2017-03-21 aborsos) diff --git a/README.md b/README.md index c73eb88..f5008f6 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ The preferred way to install this extension is through [composer](http://getcomp Either run ``` -php composer.phar require --prefer-dist mito/yii2-sentry "*" +php composer.phar require --prefer-dist mito/yii2-sentry "~1.0.0" ``` or add the following line to the require section of your `composer.json` file: ``` -"mito/yii2-sentry": "*" +"mito/yii2-sentry": "~1.0.0" ``` ## Requirements @@ -39,13 +39,13 @@ Once the extension is installed, set your configuration in common config file: ```php 'components' => [ - + 'sentry' => [ - 'class' => 'mito\sentry\SentryComponent', - 'dsn' => '', // private DSN - 'environment' => YII_CONFIG_ENVIRONMENT, // if not set, the default is `development` - 'jsNotifier' => true, // to collect JS errors - 'clientOptions' => [ // raven-js config parameter + 'class' => 'mito\sentry\Component', + 'dsn' => 'YOUR-PRIVATE-DSN', // private DSN + 'environment' => 'staging', // if not set, the default is `production` + 'jsNotifier' => true, // to collect JS errors. Default value is `false` + 'jsOptions' => [ // raven-js config parameter 'whitelistUrls' => [ // collect JS errors from these urls 'http://staging.my-product.com', 'https://my-product.com', @@ -55,14 +55,15 @@ Once the extension is installed, set your configuration in common config file: 'log' => [ 'targets' => [ [ - 'class' => 'mito\sentry\SentryTarget', + 'class' => 'mito\sentry\Target', 'levels' => ['error', 'warning'], 'except' => [ 'yii\web\HttpException:404', ], - ] + ], ], ], + ], ``` diff --git a/composer.json b/composer.json index 4773e9e..858566d 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ ], "require": { "yiisoft/yii2": "*", - "sentry/sentry": "1.5.*", - "bower-asset/raven-js": "3.7.*" + "sentry/sentry": "1.6.*", + "bower-asset/raven-js": "3.12.*" }, "require-dev": { "yiisoft/yii2-codeception": "~2.0", diff --git a/phpcs.sh b/phpcs.sh new file mode 100755 index 0000000..843bafd --- /dev/null +++ b/phpcs.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +php ./vendor/bin/phpcs --standard=vendor/mito/yii2-coding-standards/Application src +SRC=$? +php ./vendor/bin/phpcs --standard=vendor/mito/yii2-coding-standards/Application -s --exclude=PSR1.Files.SideEffects,PSR1.Classes.ClassDeclaration --extensions=php tests +TESTS=$? + +if [ $SRC -ne 0 ] || [ $TESTS -ne 0 ]; then + exit 1 +fi diff --git a/src/SentryComponent.php b/src/Component.php similarity index 57% rename from src/SentryComponent.php rename to src/Component.php index 334ad5a..c44d2eb 100644 --- a/src/SentryComponent.php +++ b/src/Component.php @@ -2,15 +2,15 @@ namespace mito\sentry; +use Closure; use mito\sentry\assets\RavenAsset; use Yii; -use yii\base\Component; -use yii\base\ErrorException; use yii\base\InvalidConfigException; +use yii\helpers\ArrayHelper; use yii\helpers\Json; use yii\web\View; -class SentryComponent extends Component +class Component extends \yii\base\Component { /** @@ -36,14 +36,7 @@ class SentryComponent extends Component * @var string environment name * @note this is ignored if [[client]] is a Raven client instance. */ - public $environment = 'development'; - - /** - * @var array Options of the Raven client. - * @see \Raven_Client::__construct for more details - * @deprecated use [[client]] instead - */ - public $options = []; + public $environment = 'production'; /** * collect JavaScript errors @@ -60,86 +53,74 @@ class SentryComponent extends Component */ public $jsOptions; - /** - * Raven-JS configuration array - * - * @var array - * @see https://docs.getsentry.com/hosted/clients/javascript/config/ - * @deprecated use [[jsOptions]] instead - */ - public $clientOptions = []; - - /** - * @var string Raven client class - * @deprecated use [[client]] instead - */ - public $ravenClass = '\Raven_Client'; - /** * @var \Raven_Client|array Raven client or configuration array used to instantiate one */ - public $client; + public $client = []; public function init() { + $this->validateDsn(); + if (!$this->enabled) { return; } - if ($this->jsOptions === null) { - $this->jsOptions = $this->clientOptions; - } - + $this->setRavenClient(); $this->setEnvironmentOptions(); + $this->generatePublicDsn(); + $this->registerAssets(); + } - // for backwards compatibility - $this->clientOptions = $this->jsOptions; - + private function validateDsn() + { if (empty($this->dsn)) { - throw new InvalidConfigException('Private or public DSN must be set!'); + throw new InvalidConfigException('Private DSN must be set!'); } - if ($this->publicDsn === null && $this->jsNotifier === true) { - $this->publicDsn = preg_replace('/^(https:\/\/|http:\/\/)([a-z0-9]*):([a-z0-9]*)@(.*)/', '$1$2@$4', $this->dsn); - } - if (!empty($this->publicDsn)) { - $this->jsNotifier = true; - } - - if (is_array($this->client)) { - $ravenClass = $this->client['class']; - $options = $this->client; - unset($options['class']); - $this->client = new $ravenClass($this->dsn, $options); - } elseif (empty($this->client)) { - // deprecated codepath - $this->client = new $this->ravenClass($this->dsn, $this->options); - } - - $this->registerAssets(); + // throws \InvalidArgumentException if dsn is invalid + \Raven_Client::parseDSN($this->dsn); } + /** + * Adds a tag to filter events by environment + */ private function setEnvironmentOptions() { if (empty($this->environment)) { return; } - if (is_array($this->client)) { - $this->client['tags']['environment'] = $this->environment; + if (is_object($this->client) && property_exists($this->client, 'tags')) { + $this->client->tags = ArrayHelper::merge($this->client->tags, ['environment' => $this->environment]); } - $this->options['tags']['environment'] = $this->environment; $this->jsOptions['tags']['environment'] = $this->environment; } + private function setRavenClient() + { + if (is_array($this->client)) { + $ravenClass = ArrayHelper::remove($this->client, 'class', '\Raven_Client'); + $options = $this->client; + $this->client = new $ravenClass($this->dsn, $options); + } elseif (!is_object($this->client) || $this->client instanceof Closure) { + $this->client = Yii::createObject($this->client); + } + + if (!is_object($this->client)) { + throw new InvalidConfigException(get_class($this) . '::' . 'client must be an object'); + } + } + /** * Registers RavenJS if publicDsn exists */ private function registerAssets() { if ($this->jsNotifier && Yii::$app instanceof \yii\web\Application) { - RavenAsset::register(Yii::$app->getView()); - Yii::$app->getView()->registerJs('Raven.config(' . Json::encode($this->publicDsn) . ', ' . Json::encode($this->jsOptions) . ').install();', View::POS_HEAD); + $view = Yii::$app->getView(); + RavenAsset::register($view); + $view->registerJs('Raven.config(' . Json::encode($this->publicDsn) . ', ' . Json::encode($this->jsOptions) . ').install();', View::POS_HEAD); } } @@ -158,21 +139,10 @@ public function capture($data, $stack = null, $vars = null) return $this->client->capture($data, $stack, $vars); } - /** - * @return \Raven_Client - * @deprecated use [[$client]] - */ - public function getClient() + private function generatePublicDsn() { - return $this->client; - } - - /** - * @return string public dsn - * @deprecated use [[$publicDsn]] - */ - public function getPublicDsn() - { - return $this->publicDsn; + if ($this->publicDsn === null && $this->jsNotifier === true) { + $this->publicDsn = preg_replace('/^(https:\/\/|http:\/\/)([a-z0-9]*):([a-z0-9]*)@(.*)/', '$1$2@$4', $this->dsn); + } } } diff --git a/src/SentryTarget.php b/src/Target.php similarity index 86% rename from src/SentryTarget.php rename to src/Target.php index 5062e06..fcaed8f 100644 --- a/src/SentryTarget.php +++ b/src/Target.php @@ -2,17 +2,14 @@ namespace mito\sentry; -use Raven_Stacktrace; -use yii\base\ErrorException; -use yii\base\InvalidConfigException; use yii\di\Instance; +use yii\helpers\VarDumper; use yii\log\Logger; -use yii\log\Target; -class SentryTarget extends Target +class Target extends \yii\log\Target { /** - * @var string|SentryComponent + * @var string|Component */ public $sentry = 'sentry'; @@ -25,7 +22,7 @@ public function init() { parent::init(); - $this->sentry = Instance::ensure($this->sentry, SentryComponent::className()); + $this->sentry = Instance::ensure($this->sentry, Component::className()); if (!$this->sentry->enabled) { $this->enabled = false; @@ -66,9 +63,10 @@ public function export() $data['message'] = $context['msg']; $extra = $context; unset($extra['msg']); - $data['extra'] = $extra; + $data['extra'] = VarDumper::export($extra); } else { - $data['message'] = $context; + $data['message'] = is_array($context) ? VarDumper::export($context) : $context; + $data['extra'] = VarDumper::export($context); } $this->sentry->capture($data, $traces); diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php index 5c771e2..8acd044 100644 --- a/tests/_bootstrap.php +++ b/tests/_bootstrap.php @@ -5,8 +5,6 @@ require_once(__DIR__ . '/../vendor/autoload.php'); require_once(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); -// autoload Dummy_Raven_Client -require_once dirname(__FILE__) . '/../vendor/sentry/sentry/test/Raven/Tests/ClientTest.php'; Raven_Autoloader::register(); $_SERVER['SCRIPT_FILENAME'] = '/' . dirname(__DIR__) . '/web'; diff --git a/tests/unit/ComponentTest.php b/tests/unit/ComponentTest.php new file mode 100644 index 0000000..b896aed --- /dev/null +++ b/tests/unit/ComponentTest.php @@ -0,0 +1,327 @@ + Component::className(), + 'enabled' => true, + 'dsn' => self::PRIVATE_DSN, + ], $options)); + } + + public function testInvalidConfigExceptionIfDsnIsNotSet() + { + $this->expectException(\yii\base\InvalidConfigException::class); + $this->mockSentryComponent([ + 'dsn' => null, + 'client' => [ + 'class' => 'mito\sentry\tests\unit\DummyRavenClient', + ], + ]); + } + + public function testInvalidConfigExceptionIfDsnIsInvalid() + { + $this->expectException(\InvalidArgumentException::class); + $this->mockSentryComponent([ + 'dsn' => 'https://getsentry.io/50', + 'client' => [ + 'class' => 'mito\sentry\tests\unit\DummyRavenClient', + ], + ]); + } + + public function testConvertPrivateDsnToPublicDsn() + { + $component = $this->mockSentryComponent([ + 'jsNotifier' => true, + 'client' => [ + 'class' => 'mito\sentry\tests\unit\DummyRavenClient', + ], + ]); + + $this->assertEquals(self::PUBLIC_DSN, $component->publicDsn); + } + + public function environments() + { + return [ + 'empty' => [null, null, self::CLIENT_CONFIG_TYPE_ARRAY, 'mito\sentry\tests\unit\DummyRavenClient'], + 'development' => [self::ENV_DEVELOPMENT, self::ENV_DEVELOPMENT, self::CLIENT_CONFIG_TYPE_ARRAY, 'mito\sentry\tests\unit\DummyRavenClient'], + 'staging' => [self::ENV_STAGING, self::ENV_STAGING, self::CLIENT_CONFIG_TYPE_ARRAY, 'mito\sentry\tests\unit\DummyRavenClient'], + 'production' => [self::ENV_PRODUCTION, self::ENV_PRODUCTION, self::CLIENT_CONFIG_TYPE_ARRAY, 'mito\sentry\tests\unit\DummyRavenClient'], + 'client config is Dummy object' => [self::ENV_DEVELOPMENT, self::ENV_DEVELOPMENT, self::CLIENT_CONFIG_TYPE_OBJECT, DummyRavenClient::class], + 'client config is object' => [self::ENV_DEVELOPMENT, self::ENV_DEVELOPMENT, self::CLIENT_CONFIG_TYPE_OBJECT, \Raven_Client::class], + 'client config is callable' => [self::ENV_DEVELOPMENT, self::ENV_DEVELOPMENT, self::CLIENT_CONFIG_TYPE_CALLABLE, function () { + return new \Raven_Client(self::PRIVATE_DSN, [ + 'tags' => ['test' => 'value'], + ]); + }], + ]; + } + + /** + * @dataProvider environments + */ + public function testSetEnvironment($environment, $expected, $configType, $clientClass) + { + switch ($configType) { + case self::CLIENT_CONFIG_TYPE_ARRAY: + $clientConfig = ['class' => $clientClass]; + break; + case self::CLIENT_CONFIG_TYPE_OBJECT: + $clientConfig = new $clientClass(self::PRIVATE_DSN, []); + break; + case self::CLIENT_CONFIG_TYPE_CALLABLE: + $clientConfig = $clientClass; + $clientClass = get_class($clientClass()); + break; + } + + $component = $this->mockSentryComponent([ + 'jsNotifier' => true, + 'environment' => $environment, + 'client' => $clientConfig, + ]); + $this->assertEquals($expected, $component->environment); + $this->assertInstanceOf($clientClass, $component->client); + if (!empty($environment)) { + $this->assertArrayHasKey('tags', $component->jsOptions); + $this->assertArrayHasKey('environment', $component->client->tags); + $this->assertEquals($expected, $component->client->tags['environment']); + $this->assertEquals($expected, $component->jsOptions['tags']['environment']); + $this->assertEquals($expected, $component->client->tags['environment']); + } + } + + public function clientConfigs() + { + return [ + 'array' => [self::CLIENT_CONFIG_TYPE_ARRAY, 'mito\sentry\tests\unit\DummyRavenClient'], + 'object' => [self::CLIENT_CONFIG_TYPE_OBJECT, DummyRavenClient::class], + 'callable' => [self::CLIENT_CONFIG_TYPE_CALLABLE, function () { + return new DummyRavenClient(self::PRIVATE_DSN, [ + 'tags' => ['test' => 'value'], + ]); + }], + ]; + } + + /** + * @dataProvider clientConfigs + */ + public function testClientConfig($configType, $clientClass) + { + switch ($configType) { + case self::CLIENT_CONFIG_TYPE_ARRAY: + $clientConfig = [ + 'class' => $clientClass, + 'tags' => [ + 'test' => 'value', + ], + ]; + break; + case self::CLIENT_CONFIG_TYPE_OBJECT: + $clientConfig = new $clientClass(self::PRIVATE_DSN, ['tags' => ['test' => 'value']]); + break; + default: + $clientConfig = $clientClass; + break; + } + $component = $this->mockSentryComponent([ + 'jsNotifier' => true, + 'environment' => self::ENV_DEVELOPMENT, + 'client' => $clientConfig, + ]); + $this->assertInstanceOf('mito\sentry\tests\unit\DummyRavenClient', $component->client); + $this->assertEquals(self::PRIVATE_DSN, $component->client->dsn); + $this->assertArrayHasKey('test', $component->client->tags); + $this->assertEquals('value', $component->client->tags['test']); + $this->assertArrayHasKey('environment', $component->client->tags); + $this->assertEquals(self::ENV_DEVELOPMENT, $component->client->tags['environment']); + } + + public function invalidConfigs() + { + return [ + 'class name - missing dsn' => ['mito\sentry\tests\unit\DummyRavenClient', \yii\base\InvalidConfigException::class], + 'string or class name - missing class' => ['RavenClient', \ReflectionException::class], + 'callable - invalid return value' => [function () { + return 'string'; + }, \yii\base\InvalidConfigException::class], + ]; + } + + /** + * @dataProvider invalidConfigs + * + * @param $config + * @param $exceptionClass + */ + public function testInvalidClientConfig($config, $exceptionClass) + { + $this->expectException($exceptionClass); + $component = $this->mockSentryComponent([ + 'jsNotifier' => true, + 'client' => $config, + ]); + } + + public function testClientConfigDefaultClass() + { + $component = $this->mockSentryComponent([ + 'client' => [ + 'curl_method' => 'async', + ], + ]); + $this->assertInstanceOf(\Raven_Client::class, $component->client); + $this->assertEquals('async', $component->client->curl_method); + } + + public function testCapture() + { + $raven = Mockery::mock('\Raven_Client'); + + $component = $this->mockSentryComponent([ + 'jsNotifier' => true, + 'environment' => self::ENV_DEVELOPMENT, + 'client' => $raven, + ]); + + $message = 'message'; + $params = ['foo' => 'bar']; + $level = 'info'; + $stack = ['stack1', 'stack2']; + $vars = ['var1' => 'value 1']; + + $data = [ + 'message' => $message, + ]; + $logger = 'test'; + $exception = new \Exception('exception message'); + + $raven->shouldReceive('captureMessage')->with($message, $params, $level, $stack, $vars)->atLeast()->once(); + $raven->shouldReceive('captureException')->with($exception, $data, $logger, $vars)->atLeast()->once(); + $raven->shouldReceive('capture')->with($data, $stack, $vars)->atLeast()->once(); + + $component->captureMessage($message, $params, $level, $stack, $vars); + $component->captureException($exception, $data, $logger, $vars); + $component->capture($data, $stack, $vars); + } + + private function assertAssetRegistered($asset) + { + if (Yii::$app->view instanceof \yii\web\View) { + $this->assertArrayHasKey($asset, Yii::$app->view->assetBundles); + } + } + + private function assertAssetNotRegistered($asset) + { + if (Yii::$app->view instanceof \yii\web\View) { + $this->assertArrayNotHasKey($asset, Yii::$app->view->assetBundles); + } + } + + public function testJsNotifierIsNotEnabledIfPublicDsnSet() + { + $publicDsn = 'https://45b4cf757v9kx53ja583f038bb1a07d6@getsentry.com/1'; + $component = $this->mockSentryComponent([ + 'publicDsn' => $publicDsn, + 'jsNotifier' => false, + 'environment' => self::ENV_DEVELOPMENT, + 'client' => [ + 'class' => 'mito\sentry\tests\unit\DummyRavenClient', + ], + ]); + $this->assertFalse($component->jsNotifier); + $this->assertEquals($publicDsn, $component->publicDsn); + $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); + } + + public function testAssetNotRegisteredIfJsNotifierIsFalseAndPublicDsnIsEmpty() + { + $component = $this->mockSentryComponent([ + 'publicDsn' => '', + 'jsNotifier' => false, + 'environment' => self::ENV_DEVELOPMENT, + 'client' => [ + 'class' => 'mito\sentry\tests\unit\DummyRavenClient', + ], + ]); + $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); + } + + public function testAssetNotRegisteredIfJsNotifierIsFalse() + { + $component = $this->mockSentryComponent([ + 'jsNotifier' => false, + 'environment' => self::ENV_DEVELOPMENT, + 'client' => [ + 'class' => 'mito\sentry\tests\unit\DummyRavenClient', + ], + ]); + $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); + } + + public function testAutogeneratedPublicDsn() + { + $component = $this->mockSentryComponent([ + 'jsNotifier' => true, + 'environment' => self::ENV_DEVELOPMENT, + 'client' => [ + 'class' => 'mito\sentry\tests\unit\DummyRavenClient', + ], + ]); + $this->assertTrue($component->jsNotifier); + $this->assertEquals(self::PUBLIC_DSN, $component->publicDsn); + $this->assertAssetRegistered('mito\sentry\assets\RavenAsset'); + } + + public function testAssetNotRegisteredIfComponentIsNotEnabled() + { + $component = $this->mockSentryComponent([ + 'enabled' => false, + 'jsNotifier' => true, + ]); + $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); + } + + public function testDoNotRegisterAssetsIfApplicationIsConsoleApplication() + { + $this->destroyApplication(); + $this->mockApplication([ + 'id' => 'testapp-console', + 'class' => '\yii\console\Application', + 'basePath' => '@mitosentry/tests/unit/runtime/web', + 'vendorPath' => '@mitosentry/vendor', + ]); + $component = $this->mockSentryComponent([ + 'enabled' => true, + 'jsNotifier' => true, + ]); + $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); + } +} diff --git a/tests/unit/SentryComponentTest.php b/tests/unit/SentryComponentTest.php deleted file mode 100644 index b1c5d35..0000000 --- a/tests/unit/SentryComponentTest.php +++ /dev/null @@ -1,224 +0,0 @@ - SentryComponent::className(), - 'enabled' => true, - 'dsn' => self::PRIVATE_DSN, - ], $options)); - } - - public function testDontCrashIfNotEnabledAndNullDSN() - { - $this->mockSentryComponent([ - 'enabled' => false, - 'dsn' => null, - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - } - - public function testInvalidConfigExceptionIfDsnIsNotSet() - { - $this->expectException(\yii\base\InvalidConfigException::class); - $this->mockSentryComponent([ - 'dsn' => null, - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - } - - public function testConvertPrivateDsnToPublicDsn() - { - $component = $this->mockSentryComponent([ - 'jsNotifier' => true, - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - - $this->assertEquals(self::PUBLIC_DSN, $component->publicDsn); - } - - public function environments() - { - return [ - 'empty' => [null, null], - 'development' => ['development', 'development'], - 'staging' => ['staging', 'staging'], - 'production' => ['production', 'production'], - ]; - } - - /** - * @dataProvider environments - */ - public function testSetEnvironment($environment, $expected) - { - $component = $this->mockSentryComponent([ - 'jsNotifier' => true, - 'environment' => $environment, - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - $this->assertEquals($expected, $component->environment); - $this->assertInstanceOf('mito\sentry\tests\unit\DummyRavenClient', $component->client); - if (!empty($environment)) { - $this->assertArrayHasKey('tags', $component->options); - $this->assertArrayHasKey('tags', $component->jsOptions); - $this->assertArrayHasKey('environment', $component->client->tags); - $this->assertEquals($expected, $component->options['tags']['environment']); - $this->assertEquals($expected, $component->jsOptions['tags']['environment']); - $this->assertEquals($expected, $component->client->tags['environment']); - } - } - - public function testClientConfig() - { - $component = $this->mockSentryComponent([ - 'jsNotifier' => true, - 'environment' => 'development', - 'client' => [ - 'class' => 'mito\sentry\tests\unit\DummyRavenClient', - 'tags' => [ - 'test' => 'value', - ], - ], - ]); - $this->assertInstanceOf('mito\sentry\tests\unit\DummyRavenClient', $component->client); - $this->assertEquals(self::PRIVATE_DSN, $component->client->dsn); - $this->assertArrayHasKey('test', $component->client->tags); - $this->assertEquals('value', $component->client->tags['test']); - $this->assertArrayHasKey('environment', $component->client->tags); - $this->assertEquals('development', $component->client->tags['environment']); - $this->assertEquals($component->client, $component->getClient()); - } - - public function testCapture() - { - $raven = Mockery::mock('\Raven_Client'); - - $component = $this->mockSentryComponent([ - 'jsNotifier' => true, - 'environment' => 'development', - 'client' => $raven, - ]); - - $message = 'message'; - $params = ['foo' => 'bar']; - $level = 'info'; - $stack = ['stack1', 'stack2']; - $vars = ['var1' => 'value 1']; - - $data = [ - 'message' => $message, - ]; - $logger = 'test'; - $exception = new \Exception('exception message'); - - $raven->shouldReceive('captureMessage')->with($message, $params, $level, $stack, $vars)->atLeast()->once(); - $raven->shouldReceive('captureException')->with($exception, $data, $logger, $vars)->atLeast()->once(); - $raven->shouldReceive('capture')->with($data, $stack, $vars)->atLeast()->once(); - - $component->captureMessage($message, $params, $level, $stack, $vars); - $component->captureException($exception, $data, $logger, $vars); - $component->capture($data, $stack, $vars); - } - - private function assertAssetRegistered($asset) - { - if (Yii::$app->view instanceof \yii\web\View) { - $this->assertArrayHasKey($asset, Yii::$app->view->assetBundles); - } - } - - private function assertAssetNotRegistered($asset) - { - if (Yii::$app->view instanceof \yii\web\View) { - $this->assertArrayNotHasKey($asset, Yii::$app->view->assetBundles); - } - } - - public function testJsNotifierEnabledIfPublicDsnSet() - { - $publicDsn = 'https://45b4cf757v9kx53ja583f038bb1a07d6@getsentry.com/1'; - $component = $this->mockSentryComponent([ - 'publicDsn' => $publicDsn, - 'jsNotifier' => false, - 'environment' => 'development', - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - $this->assertTrue($component->jsNotifier); - $this->assertEquals($publicDsn, $component->publicDsn); - $this->assertEquals($component->publicDsn, $component->getPublicDsn()); - $this->assertAssetRegistered('mito\sentry\assets\RavenAsset'); - } - - public function testAssetNotRegisteredIfJsNotifierIsFalseAndPublicDsnIsEmpty() - { - $component = $this->mockSentryComponent([ - 'publicDsn' => '', - 'jsNotifier' => false, - 'environment' => 'development', - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); - } - - public function testAssetNotRegisteredIfJsNotifierIsFalse() - { - $component = $this->mockSentryComponent([ - 'jsNotifier' => false, - 'environment' => 'development', - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); - } - - public function testAutogeneratedPublicDsn() - { - $component = $this->mockSentryComponent([ - 'jsNotifier' => true, - 'environment' => 'development', - 'ravenClass' => 'mito\sentry\tests\unit\DummyRavenClient', - ]); - $this->assertTrue($component->jsNotifier); - $this->assertEquals(self::PUBLIC_DSN, $component->publicDsn); - $this->assertAssetRegistered('mito\sentry\assets\RavenAsset'); - } - - public function testAssetNotRegisteredIfComponentIsNotEnabled() - { - $component = $this->mockSentryComponent([ - 'enabled' => false, - 'jsNotifier' => true, - ]); - $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); - } - - public function testDoNotRegisterAssetsIfApplicationIsConsoleApplication() - { - $this->destroyApplication(); - $this->mockApplication([ - 'id' => 'testapp-console', - 'class' => '\yii\console\Application', - 'basePath' => '@mitosentry/tests/unit/runtime/web', - 'vendorPath' => '@mitosentry/vendor', - ]); - $component = $this->mockSentryComponent([ - 'enabled' => true, - 'jsNotifier' => true, - ]); - $this->assertAssetNotRegistered('mito\sentry\assets\RavenAsset'); - } -} diff --git a/tests/unit/SentryTargetTest.php b/tests/unit/TargetTest.php similarity index 94% rename from tests/unit/SentryTargetTest.php rename to tests/unit/TargetTest.php index 4364626..e8ec99d 100644 --- a/tests/unit/SentryTargetTest.php +++ b/tests/unit/TargetTest.php @@ -2,36 +2,31 @@ namespace mito\sentry\tests\unit; -use mito\sentry\SentryTarget; -use mito\sentry\SentryComponent; +use mito\sentry\Target; +use mito\sentry\Component; use yii\log\Logger; use yii\helpers\ArrayHelper; use Yii; use Mockery; use yii\web\HttpException; -class SentryTargetTest extends \yii\codeception\TestCase +class TargetTest extends \yii\codeception\TestCase { const EXCEPTION_TYPE_OBJECT = 'object'; const EXCEPTION_TYPE_MSG = 'message'; const EXCEPTION_TYPE_STRING = 'string'; + const EXCEPTION_TYPE_ARRAY = 'array'; const DEFAULT_ERROR_MESSAGE = 'message'; public $appConfig = '@mitosentry/tests/unit/config/main.php'; - private $_exceptionTypes = [ - self::EXCEPTION_TYPE_OBJECT, - self::EXCEPTION_TYPE_MSG, - self::EXCEPTION_TYPE_STRING, - ]; - protected function mockSentryTarget($options = []) { - $component = Mockery::mock(SentryComponent::className()); + $component = Mockery::mock(Component::className()); return Yii::createObject(ArrayHelper::merge([ - 'class' => SentryTarget::className(), + 'class' => Target::className(), 'sentry' => $component, ], $options)); } @@ -53,7 +48,7 @@ public function testDontCrashIfComponentIsDisabled() // Attempting to call any method on sentryComponent will throw an exception. // If the component is disabled, SentryTarget should not call any methods on // SentryComponent. - $component = Mockery::mock(SentryComponent::className()); + $component = Mockery::mock(Component::className()); $component->enabled = false; $target = $this->mockSentryTarget([ 'sentry' => $component, @@ -119,7 +114,7 @@ public function testFilterExceptions($except, $exceptionClass, $exceptionCode, $ } else { $target->sentry->shouldReceive('capture') ->with(Mockery::on(function ($data) { - return $data['message'] === self::DEFAULT_ERROR_MESSAGE; + return !empty($data['message']); }), Mockery::on(function ($traces) { return true; }))->once(); @@ -219,6 +214,7 @@ public function exceptFilters() 'catch code 503' => [['except' => ['yii\web\HttpException:404']], HttpException::class, 503, true, self::EXCEPTION_TYPE_OBJECT], 'catch string' => [['except' => ['yii\web\HttpException:404']], null, null, true, self::EXCEPTION_TYPE_STRING], 'catch message' => [['except' => ['yii\web\HttpException:404']], null, null, true, self::EXCEPTION_TYPE_MSG], + 'catch array' => [['except' => ['yii\web\HttpException:404']], null, null, true, self::EXCEPTION_TYPE_ARRAY], ]; // php7+ @@ -229,8 +225,6 @@ public function exceptFilters() ]); } - $results = array_merge($results, []); - return $results; } @@ -267,6 +261,12 @@ private function createException($type, $exceptionClass, $exceptionCode) case self::EXCEPTION_TYPE_STRING: $exception = self::DEFAULT_ERROR_MESSAGE; break; + case self::EXCEPTION_TYPE_ARRAY: + $exception = [ + 'message' => self::DEFAULT_ERROR_MESSAGE, + 'other' => 'extra message', + ]; + break; default: $exception = false; }