First: Mock the Actual Controller to return Exception
namespace App\Tests\Integration\Sentry\Mock;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class UnauthorizedHttpExceptionController
{
public function index(): JsonResponse
{
throw new UnauthorizedHttpException('Unauthorized Http Exception!');
}
}
Then, Actual Integration Test
namespace App\Tests\Integration\Sentry;
use App\Controller\PingController;
use App\Entity\UserDB\User;
use App\Tests\Integration\Controller\ControllerIntegrationTestCase;
use App\Tests\Integration\Sentry\Mock\AccessDeniedExceptionController;
use App\Tests\Integration\Sentry\Mock\DruidExceptionController;
use App\Tests\Integration\Sentry\Mock\NotFoundHttpExceptionController;
use App\Tests\Integration\Sentry\Mock\TooManyRequestsHttpExceptionController;
use App\Tests\Integration\Sentry\Mock\UnauthorizedHttpExceptionController;
use App\Tests\Integration\Sentry\Mock\UnknownUserExceptionController;
use Generator;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class LoggerExceptionTest extends ControllerIntegrationTestCase
{
/**
* @var MockResponse
*/
private $mockResponse;
public function setUp(): void
{
parent::setUp();
$this->mockResponse = new MockResponse();
}
public function testOnDruidExceptionSendLogTagIsFoundOnSentryRequest(): void
{
$this->mockHttpClient();
self::$container->set(PingController::class, new DruidExceptionController());
$this->jsonRequest('GET', '/v3/ping');
$result = json_decode($this->mockResponse->getRequestOptions()['body']);
self::assertObjectHasAttribute('tags', $result);
self::assertObjectHasAttribute('log', $result->tags);
self::assertObjectHasAttribute('system', $result->tags);
self::assertSame('sentry', $result->tags->log);
self::assertSame('druid', $result->tags->system);
}
/**
* @dataProvider sentryIgnoreErrorExceptionsDataProvider
*/
public function testSentryIgnoreErrorsExceptionDoesNotSendRequestToSentry($sentryIgnoreErrorException): void
{
$this->mockHttpClient();
self::$container->set(PingController::class, $sentryIgnoreErrorException);
$this->jsonRequest('GET', '/v3/ping');
$requestBody = $this->mockResponse->getRequestOptions();
self::assertEmpty($requestBody);
}
/**
* @dataProvider logExceptionEqualOrHigherThanErrorDataProvider
*/
public function testOnLogExceptionEqualOrHigherThanErrorExceptionSentryLogTagIsFoundOnSentryRequest(string $exceptionType): void
{
$this->mockHttpClient();
$logger = self::$container->get(LoggerInterface::class);
$logger->{$exceptionType}('Some exception Message');
$requestBody = $this->mockResponse->getRequestOptions()['body'];
$result = json_decode($requestBody);
self::assertObjectHasAttribute('tags', $result);
self::assertObjectHasAttribute('log', $result->tags);
self::assertSame('sentry', $result->tags->log);
}
/**
* @dataProvider logExceptionLowerThanErrorDataProvider
*/
public function testOnLogExceptionLowerThanErrorExceptionNoSentryRequestIsSent(string $exceptionType): void
{
$this->mockHttpClient();
$logger = self::$container->get(LoggerInterface::class);
$logger->{$exceptionType}('Some Exception Message');
$requestBody = $this->mockResponse->getRequestOptions();
self::assertEmpty($requestBody);
}
public function testOnLogErrorExceptionUserInformationIsFound(): void
{
$this->setIpAddressOnRequest();
$this->setTokenStorage();
$this->mockHttpClient();
$logger = self::$container->get(LoggerInterface::class);
$logger->error('Some Error Message');
$requestBody = $this->mockResponse->getRequestOptions()['body'];
$result = json_decode($requestBody);
self::assertObjectHasAttribute('user', $result);
self::assertObjectHasAttribute('id', $result->user);
self::assertSame('123', $result->user->id);
self::assertSame('127.0.0.1', $result->user->ip_address);
}
public function testOnLogErrorExceptionExtraDataIsFoundIfRequestIdIsSetOnRequest(): void
{
$this->setRequestIdOnRequest();
$this->mockHttpClient();
$logger = self::$container->get(LoggerInterface::class);
$logger->error('Some Error Message');
$requestBody = $this->mockResponse->getRequestOptions()['body'];
$result = json_decode($requestBody);
self::assertObjectHasAttribute('extra', $result);
self::assertSame('234', $result->extra->request_id);
}
public function logExceptionEqualOrHigherThanErrorDataProvider(): Generator
{
yield ['error'];
yield ['critical'];
yield ['alert'];
yield ['emergency'];
}
public function logExceptionLowerThanErrorDataProvider(): Generator
{
yield ['warning'];
yield ['notice'];
yield ['info'];
yield ['debug'];
}
public function sentryIgnoreErrorExceptionsDataProvider(): Generator
{
yield [new UnknownUserExceptionController()];
yield [new AccessDeniedExceptionController()];
yield [new UnauthorizedHttpExceptionController()];
yield [new TooManyRequestsHttpExceptionController()];
yield [new NotFoundHttpExceptionController()];
}
private function setTokenStorage(): void
{
$user = new User();
$user->setId('123');
$token = new UsernamePasswordToken(
$user,
null,
'secure_area',
['ROLE_ADMIN']
);
static::$kernel
->getContainer()
->get('security.token_storage')
->setToken($token);
}
private function setIpAddressOnRequest(): void
{
$requestStack = self::$container->get('request_stack');
$requestStack->push(
new Request(
[],
[],
[],
[],
[],
['REMOTE_ADDR' => '127.0.0.1']
)
);
}
private function setRequestIdOnRequest(): void
{
$requestStack = self::$container->get('request_stack');
$requestStack->push(
new Request(
[],
[],
[],
[],
[],
['REQUEST_ID' => '234']
)
);
}
private function mockHttpClient(): void
{
$mockClient = new MockHttpClient($this->mockResponse);
$psr18client = new Psr18Client($mockClient);
$roundRobinClient = self::$container->get('http.async.test.client');
$roundRobinClient->addHttpClient($psr18client);
}
}
Also, need to configure services_yaml on test to configure mock http dependencies
services:
_defaults:
autowire: true
autoconfigure: true
public: true
App\Security\UserProvider:
public: true
Symfony\Component\Security\Core\Security:
public: true
#
App\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
security.authorization_checker:
class: Symfony\Component\Security\Core\Authorization\AuthorizationChecker
public: true
App\Service\AnalyticsService:
public: true
ph.rate_limiting.test.calculation.analytics.api:
alias: ph.rate_limiting.calculation.analytics.api
public: true
ph.rate_limiting.test.calculation.souq.analytics.api:
alias: ph.rate_limiting.calculation.souq.analytics.api
public: true
# Public service definitions - required in order to replace services in tests
# If the service identifier is an interface, you'll have to manually map it back to
# its implementation
App\Repository\UserDB\AnalyticsConsoleUserStateRepository:
App\Repository\UserDB\CampaignRepository:
App\Repository\UserDB\CampaignTermsRepository:
App\Repository\UserDB\CampaignTermsAcknowledgementRepository:
App\Repository\UserDB\CommissionGroupRepositoryInterface:
class: App\Repository\UserDB\CommissionGroupRepository
App\Repository\UserDB\CreativeTagRepository:
App\Repository\UserDB\NetworkRepositoryInterface:
class: App\Repository\UserDB\NetworkRepository
App\Repository\UserDB\NetworkTermsRepositoryInterface:
class: App\Repository\UserDB\NetworkTermsRepository
App\Repository\UserDB\PartnerRepository:
App\Repository\UserDB\PartnerGroupRepository:
App\Repository\UserDB\ParticipationRepository:
App\Repository\UserDB\UserRepository:
App\Repository\UserDB\NetworkRepository:
App\Repository\UserDB\FeatureRepositoryInterface:
class: App\Repository\UserDB\FeatureRepository
App\Repository\UserDB\UserResourceActionRepositoryInterface:
class: App\Repository\UserDB\UserResourceActionRepository
App\Service\PartnerizeTagService:
App\Service\NetworkService:
App\Service\FeatureService:
App\Service\PartnerProspects\ProspectInvitationService:
arguments: {
$gatewaySourceName: 'my_test_gateway'
}
App\Service\Terms\NetworkTermsService:
PH\UarBundle\Service\UserService:
App\Repository\InputDB\AutogeneratedInvoiceRepositoryInterface:
App\Service\AutogeneratedInvoiceReportService:
PH\RateLimitingBundle\Services\RateLimitingServiceManager:
psr18.druid:
class: App\Tests\Integration\Mocks\PsrHttpClientProxyMock
http.async.test.client:
class: Http\Client\Common\HttpClientPool\RoundRobinClientPool
Sentry\Transport\TransportFactoryInterface:
class: Sentry\SentryBundle\Transport\TransportFactory
arguments:
$httpClient: '@http.async.test.client'
$logger: null