Skip to content

Commit

Permalink
Merge pull request #2 from vimeo/add-event-dispatching
Browse files Browse the repository at this point in the history
Add event dispatching support
  • Loading branch information
JCManzo authored May 10, 2019
2 parents e68880c + 9722efe commit 2452b95
Show file tree
Hide file tree
Showing 30 changed files with 360 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ composer.lock
composer-psalm.lock
composer.phar
/vendor
.idea
3 changes: 2 additions & 1 deletion composer-psalm.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
}
},
"require": {
"omnipay/common": "~2.0"
"omnipay/common": "~2.0",
"vimeo/payment-gateway-logger": "^1.0"
},
"require-dev": {
"omnipay/tests": "~2.0",
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
}
},
"require": {
"omnipay/common": "~2.0"
"omnipay/common": "~2.0",
"vimeo/payment-gateway-logger": "^1.0",
"ext-simplexml": "*"
},
"require-dev": {
"omnipay/tests": "~2.0"
Expand Down
6 changes: 3 additions & 3 deletions psalm_plugins/StringChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ class StringChecker extends \Psalm\Plugin
* Checks an expression
*
* @param StatementsChecker $statements_checker
* @param \PhpParser\Node\Expr $stmt
* @param PhpParser\Node\Expr $stmt
* @param Context $context
* @param CodeLocation $code_location
* @param array<string> $suppressed_issues
* @return null|false
*/
public function checkExpression(
StatementsChecker $statements_checker,
\PhpParser\Node\Expr $stmt,
PhpParser\Node\Expr $stmt,
Context $context,
CodeLocation $code_location,
array $suppressed_issues
Expand All @@ -37,7 +37,7 @@ public function checkExpression(

$project_checker = $statements_checker->getFileChecker()->project_checker;
if (Checker\ClassChecker::checkFullyQualifiedClassLikeName(
$project_checker,
$statements_checker,
$fq_class_name,
$code_location,
$suppressed_issues
Expand Down
61 changes: 47 additions & 14 deletions src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

namespace Omnipay\BlueSnap\Message;

use DateTime;
use Exception;
use Guzzle\Http\Message\RequestInterface;
use Omnipay\BlueSnap\Constants;
use Omnipay\Common\Exception\InvalidRequestException;
use SimpleXMLElement;
use Omnipay\Common\Exception\RuntimeException;
use DateTime;
use PaymentGatewayLogger\Event\Constants as PaymentGatewayLoggerConstants;
use PaymentGatewayLogger\Event\ErrorEvent;
use PaymentGatewayLogger\Event\RequestEvent;
use PaymentGatewayLogger\Event\ResponseEvent;
use SimpleXMLElement;

/**
* BlueSnap Abstract Request
Expand Down Expand Up @@ -300,7 +306,9 @@ protected function validateTimeZone(DateTime $datetime)
*
* @param SimpleXMLElement|null $data
* @return Response
*
* @throws RuntimeException if $data is invalid XML
* @throws Exception if there is a problem when initiating the request.
* @psalm-suppress TypeDoesNotContainType psalm bug with SimpleXMLElement: https://github.com/vimeo/psalm/issues/145
*/
public function sendData($data)
Expand All @@ -317,8 +325,10 @@ public function sendData($data)
}
}

$eventDispatcher = $this->httpClient->getEventDispatcher();

// don't throw exceptions for errors
$this->httpClient->getEventDispatcher()->addListener(
$eventDispatcher->addListener(
// @codingStandardsIgnoreStart
'request.error',
/**
Expand All @@ -333,24 +343,41 @@ function ($event) {
}
);

/** @var RequestInterface $httpRequest */
$httpRequest = $this->httpClient->createRequest(
$this->getHttpMethod(),
$this->getEndpoint(),
null,
$data
);

/**
* @var \Guzzle\Http\Message\RequestInterface
*/
$httpRequest = $httpRequest
->setHeader(
'Authorization',
'Basic ' . base64_encode(($this->getUsername() ?: '') . ':' . ($this->getPassword() ?: ''))
)
->setHeader('Content-Type', 'application/xml')
->setHeader('bluesnap-version', self::API_VERSION);
$httpResponse = $httpRequest->send();
$httpRequest
->setHeader(
'Authorization',
'Basic ' . base64_encode(($this->getUsername() ?: '') . ':' . ($this->getPassword() ?: ''))
)
->setHeader('Content-Type', 'application/xml')
->setHeader('bluesnap-version', self::API_VERSION);

// Fire a request event before sending request.
$eventDispatcher->dispatch(
PaymentGatewayLoggerConstants::OMNIPAY_REQUEST_BEFORE_SEND,
new RequestEvent($this)
);

$httpResponse = null;
try {

$httpResponse = $httpRequest->send();
} catch (Exception $e) {
// Fire an error event if there was a problem with the request.
$eventDispatcher->dispatch(
PaymentGatewayLoggerConstants::OMNIPAY_REQUEST_ERROR,
new ErrorEvent($e, $this)
);

throw $e;
}

// responses can be XML, JSON, or plain text depending on the request and whether it's successful
try {
Expand All @@ -375,6 +402,12 @@ function ($event) {
$this->response->setRequestId($request_id_header ? strval($request_id_header) : null);
}

// Log the successful request's response.
$eventDispatcher->dispatch(
PaymentGatewayLoggerConstants::OMNIPAY_RESPONSE_SUCCESS,
new ResponseEvent($this->response)
);

return $this->response;
}

Expand Down
1 change: 1 addition & 0 deletions src/Message/HostedCheckoutDecryptReturnUrlRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class HostedCheckoutDecryptReturnUrlRequest extends ExtendedAbstractRequest
{
/**
* @return SimpleXMLElement
* @psalm-suppress PossiblyInvalidArrayAccess because the existence of the key is checked first before using it.
*/
public function getData()
{
Expand Down
1 change: 1 addition & 0 deletions src/Message/IPNCallback.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class IPNCallback
* $ipn can be a full URL, just the query string, or the value of the $_POST variable.
*
* @param string|array<string, string> $ipn
* @psalm-suppress PossiblyInvalidArrayAccess because the existence of the key is checked first before using it.
*/
public function __construct($ipn)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Message/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public function setCode($code)
* Get the error message from the response. Returns null if request was successful.
*
* @return string|null
* @psalm-suppress MixedPropertyFetch because we check the data typing before using.
*/
public function getMessage()
{
Expand Down Expand Up @@ -428,7 +429,7 @@ public function getTransactions()
public function getSubscriptions()
{
// data can be SimpleXMLElement or JSON object
if (!isset($this->data['data']) && !isset($this->data)) {
if (!isset($this->data) && !isset($this->data['data'])) {
return null;
}

Expand Down
172 changes: 172 additions & 0 deletions tests/EventEmitterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

namespace Omnipay\BlueSnap;

use Exception;
use Guzzle\Http\Client;
use Guzzle\Http\Message\RequestInterface;
use Omnipay\BlueSnap\Message\ExtendedCancelSubscriptionRequest;
use Omnipay\BlueSnap\Test\Framework\OmnipayBlueSnapTestCase;
use Omnipay\BlueSnap\Test\Framework\TestSubscriber;
use Omnipay\Common\Message\ResponseInterface;
use PaymentGatewayLogger\Event\Constants;
use PaymentGatewayLogger\Event\ErrorEvent;
use PaymentGatewayLogger\Event\RequestEvent;
use PaymentGatewayLogger\Event\ResponseEvent;
use PaymentGatewayLogger\Test\Framework\TestLogger;
use Symfony\Component\EventDispatcher\EventDispatcher;

class EventEmitterTest extends OmnipayBlueSnapTestCase
{
/**
* @var string
*/
private $subscriptionReference;

/**
* @var TestSubscriber
*/
private $testSubscriber;

/**
* @return void
*/
protected function setUp()
{
$this->testSubscriber = new TestSubscriber('test_gateway', new TestLogger());
$this->subscriptionReference = '123';

parent::setUp();
}

/**
* Ensures that 'Request' and 'Response' events are emitted when issuing a request.
*
* @return void
*/
public function testAuthorizeRequestSuccessfulResponseEmitted()
{
$this->setMockHttpResponse('ExtendedCancelSubscriptionSuccess.txt', array(
'SUBSCRIPTION_REFERENCE' => $this->subscriptionReference
));

/** @var Client $customHttpClient */
$customHttpClient = $this->getHttpClient();
$eventDispatcher = $customHttpClient->getEventDispatcher();

$eventDispatcher->addSubscriber($this->testSubscriber);

$request = new ExtendedCancelSubscriptionRequest($customHttpClient, $this->getHttpRequest());
$request->setSubscriptionReference($this->subscriptionReference);

$class = $this;
$eventDispatcher
->addListener(
Constants::OMNIPAY_REQUEST_BEFORE_SEND,
/** @return void */
function (RequestEvent $event) use ($class) {
/** @var \Omnipay\Common\Message\RequestInterface $request */
$request = $event['request'];
$class->assertInstanceOf('Omnipay\BlueSnap\Message\ExtendedCancelSubscriptionRequest', $request);
}
);

$eventDispatcher
->addListener(
Constants::OMNIPAY_RESPONSE_SUCCESS,
/** @return void */
function (ResponseEvent $event) use ($class) {
/** @var ResponseInterface $response */
$response = $event['response'];
$class->assertInstanceOf('\Omnipay\BlueSnap\Message\Response', $response);
}
);

$response = $request->send();
$this->assertTrue($response->isSuccessful());

$eventsDispatched = $this->testSubscriber->eventsDispatched;

$this->assertEquals(1, $eventsDispatched[Constants::OMNIPAY_REQUEST_BEFORE_SEND]);
$this->assertEquals(1, $eventsDispatched[Constants::OMNIPAY_RESPONSE_SUCCESS]);
$this->assertArrayNotHasKey(Constants::OMNIPAY_REQUEST_ERROR, $eventsDispatched);
}

/**
* Ensures that 'Request' and 'Error' events are emitted when issuing an improper request.
*
* @psalm-suppress UndefinedMethod because Psalm can't infer that it exists in the Mock object but it does!
* @return void
*/
public function testAuthorizeRequestErrorEventEmitted()
{
$this->setMockHttpResponse('ExtendedCancelSubscriptionFailure.txt', array(
'SUBSCRIPTION_REFERENCE' => $this->subscriptionReference
));

/** @var Client $customHttpClient */
$customHttpClient = $this->getMock(
'Guzzle\Http\Client',
array('getEventDispatcher', 'addSubscriber', 'createRequest')
);
$customHttpClient->method('getEventDispatcher')->willReturn(new EventDispatcher());

// Mock the Guzzle request so that it throws an error
$guzzle_request_mock = $this->getMock(
'Guzzle\Http\Message\EntityEnclosingRequest',
array('setHeader'),
array(RequestInterface::POST, 'https://www.test.com')
);
$guzzle_request_mock->method('setHeader')->willReturnSelf();
$customHttpClient->method('createRequest')->willReturn($guzzle_request_mock);

/** @var EventDispatcher $eventDispatcher */
$eventDispatcher = $customHttpClient->getEventDispatcher();
$eventDispatcher->addSubscriber($this->testSubscriber);

$request = new ExtendedCancelSubscriptionRequest($customHttpClient, $this->getHttpRequest());
$request->setSubscriptionReference($this->subscriptionReference);

$class = $this;
$eventDispatcher
->addListener(
Constants::OMNIPAY_REQUEST_BEFORE_SEND,
/** @return void */
function (RequestEvent $event) use ($class) {
/** @var \Omnipay\Common\Message\RequestInterface $request */
$request = $event['request'];
$class->assertInstanceOf('Omnipay\BlueSnap\Message\ExtendedCancelSubscriptionRequest', $request);
}
);

$eventDispatcher
->addListener(
Constants::OMNIPAY_REQUEST_ERROR,
/** @return void */
function (ErrorEvent $event) use ($class) {
/** @var \Error $error */
$error = $event['error'];
$class->assertInstanceOf('Guzzle\Common\Exception\RuntimeException', $error);
}
);

$eventsDispatched = array();
$response = null;
try {
$response = $request->send();
$this->fail();
} catch (Exception $exception) {
// We want to resume program execution to check events in $eventsDispatched.
$eventsDispatched = $this->testSubscriber->eventsDispatched;
$this->assertEquals('A client must be set on the request', $exception->getMessage());
}

$this->assertNull($response);

// An exception will always be expected. Therefore $eventsDispatched should never be empty.
$this->assertNotEmpty($eventsDispatched);
$this->assertEquals(1, $eventsDispatched[Constants::OMNIPAY_REQUEST_BEFORE_SEND]);
$this->assertEquals(1, $eventsDispatched[Constants::OMNIPAY_REQUEST_ERROR]);
$this->assertArrayNotHasKey(Constants::OMNIPAY_RESPONSE_SUCCESS, $eventsDispatched);
}
}
Loading

0 comments on commit 2452b95

Please sign in to comment.