jenkins-bot has submitted this change and it was merged.

Change subject: Mock Adyen API and queue, test capture job
......................................................................


Mock Adyen API and queue, test capture job

In which we plumb the depths of the conflict between static
untestibility and configurable dependency injection.

Nothing was using Context::set, so I changed the behavior to allow
resetting the instance to null.

Bug: T127880
Change-Id: I4aae0d4fce2275920597867bf2e56d569ce6738d
---
M Core/Context.php
M PaymentProviders/Adyen/AdyenPaymentsAPI.php
A PaymentProviders/Adyen/AdyenPaymentsInterface.php
M PaymentProviders/Adyen/Jobs/ProcessCaptureRequestJob.php
A PaymentProviders/Adyen/Tests/Data/auth.json
A PaymentProviders/Adyen/Tests/Data/pending.json
A PaymentProviders/Adyen/Tests/MockAdyenPaymentsAPI.php
A PaymentProviders/Adyen/Tests/config_test_failure.php
A PaymentProviders/Adyen/Tests/config_test_success.php
A PaymentProviders/Adyen/Tests/phpunit/CaptureJobTest.php
M PaymentProviders/Amazon/Tests/phpunit/ApiTest.php
M Tests/BaseSmashPigUnitTestCase.php
A Tests/MockDataStore.php
M Tests/bootstrap-phpunit.php
M config_defaults.php
15 files changed, 401 insertions(+), 31 deletions(-)

Approvals:
  Awight: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/Core/Context.php b/Core/Context.php
index f36cdf3..ec0bafd 100644
--- a/Core/Context.php
+++ b/Core/Context.php
@@ -48,7 +48,7 @@
         */
        public static function set( Context $c = null ) {
                $old = Context::$instance;
-               Context::$instance = ( $c === null ) ? new Context() : $c;
+               Context::$instance = $c;
 
                return $old;
        }
diff --git a/PaymentProviders/Adyen/AdyenPaymentsAPI.php 
b/PaymentProviders/Adyen/AdyenPaymentsAPI.php
index a61b43d..9a1ec66 100644
--- a/PaymentProviders/Adyen/AdyenPaymentsAPI.php
+++ b/PaymentProviders/Adyen/AdyenPaymentsAPI.php
@@ -1,16 +1,15 @@
 <?php namespace SmashPig\PaymentProviders\Adyen;
 
 use SmashPig\Core\Context;
-use SmashPig\Core\Configuration;
 use SmashPig\Core\Logging\Logger;
 use SmashPig\Core\Logging\TaggedLogger;
 
-class AdyenPaymentsAPI {
+class AdyenPaymentsAPI implements AdyenPaymentsInterface {
 
        protected $soapClient = null;
        protected $account = '';
 
-       public function __construct( $account ) {
+       public function setAccount( $account ) {
                require_once( 'WSDL/Payment.php' );
 
                $this->account = $account;
@@ -26,16 +25,6 @@
                );
        }
 
-       /**
-        * Performs a Capture modification to a given Adyen transaction.
-        *
-        * @param string    $currency       Original currency of the request
-        * @param int       $amount         Amount to be captured. Less than or 
equal to the original request
-        * @param string    $pspReference   Original pspReference of the request
-        *
-        * @returns bool|string Result will be false on SOAP exception or 
remote request denial. If request was successful
-        * the return result will be a pspReference string to this modification.
-        */
        public function capture( $currency, $amount, $pspReference ) {
                $data = new WSDL\capture();
                $data->modificationRequest = new WSDL\ModificationRequest();
@@ -64,14 +53,6 @@
                }
        }
 
-       /**
-        * Cancels an Adyen authorization
-        *
-        * @param string $pspReference Original pspReference of the request
-        *
-        * @returns bool|string Result will be false on SOAP exception or 
remote request denial.
-        * If request was successful the return result will be a pspReference 
string to this modification.
-        */
        public function cancel( $pspReference ) {
                $data = new WSDL\cancel();
                $data->modificationRequest = new WSDL\ModificationRequest();
diff --git a/PaymentProviders/Adyen/AdyenPaymentsInterface.php 
b/PaymentProviders/Adyen/AdyenPaymentsInterface.php
new file mode 100644
index 0000000..4750d35
--- /dev/null
+++ b/PaymentProviders/Adyen/AdyenPaymentsInterface.php
@@ -0,0 +1,32 @@
+<?php namespace SmashPig\PaymentProviders\Adyen;
+
+interface AdyenPaymentsInterface {
+
+       /**
+        * Indicate which merchant account to use for API calls
+        * @param string $account the merchant account code
+        */
+       public function setAccount( $account );
+
+       /**
+        * Performs a Capture modification to a given Adyen transaction.
+        *
+        * @param string $currency Original currency of the request
+        * @param int $amount Amount to be captured. Less than or equal to the 
original request
+        * @param string $pspReference Original pspReference of the request
+        *
+        * @return bool|string Result will be false on SOAP exception or remote 
request denial.
+        * If request was successful the return result will be a pspReference 
string to this modification.
+        */
+       public function capture( $currency, $amount, $pspReference );
+
+       /**
+        * Cancels an Adyen authorization
+        *
+        * @param string $pspReference Original pspReference of the request
+        *
+        * @return bool|string Result will be false on SOAP exception or remote 
request denial.
+        * If request was successful the return result will be a pspReference 
string to this modification.
+        */
+       public function cancel( $pspReference );
+}
diff --git a/PaymentProviders/Adyen/Jobs/ProcessCaptureRequestJob.php 
b/PaymentProviders/Adyen/Jobs/ProcessCaptureRequestJob.php
index d0cc7e6..d6d5987 100644
--- a/PaymentProviders/Adyen/Jobs/ProcessCaptureRequestJob.php
+++ b/PaymentProviders/Adyen/Jobs/ProcessCaptureRequestJob.php
@@ -56,6 +56,9 @@
                // Determine if a message exists in the pending queue; if it 
does not then
                // this payment has already been sent to the verified queue.
                Logger::debug( 'Attempting to locate associated message in 
pending queue.' );
+               /**
+                * @var \SmashPig\Core\DataStores\KeyedOpaqueDataStore
+                */
                $pendingQueue = Configuration::getDefaultConfig()->object( 
'data-store/pending' );
                $queueMessage = $pendingQueue->queueGetObject( null, 
$this->correlationId );
                $success = true;
@@ -66,7 +69,7 @@
                        $pendingQueue->queueIgnoreObject();
 
                        // Attempt to capture the payment
-                       $api = new AdyenPaymentsAPI( $this->account );
+                       $api = $this->getApi();
                        Logger::info(
                                "Attempting capture API call for currency 
'{$this->currency}', " .
                                "amount '{$this->amount}', reference 
'{$this->pspReference}'."
@@ -91,7 +94,7 @@
                        }
                } else if ( $action == self::ACTION_REJECT ) {
                        Logger::debug( "Cancelling authorization with reference 
'{$this->pspReference}'" );
-                       $api = new AdyenPaymentsAPI( $this->account );
+                       $api = $this->getApi();
                        $result = $api->cancel( $this->pspReference );
                        if ( $result ) {
                                Logger::debug( "Successfully cancelled 
authorization" );
@@ -166,4 +169,13 @@
                Logger::debug( "Sending antifraud message with risk score 
$riskScore and action $action." );
                Configuration::getDefaultConfig()->object( 
'data-store/antifraud' )->addObject( $antifraudMessage );
        }
+
+       /**
+        * @return \SmashPig\PaymentProviders\Adyen\AdyenPaymentsInterface
+        */
+       protected function getApi() {
+               $api = Configuration::getDefaultConfig()->object( 
'payment-provider/adyen/api' );
+               $api->setAccount( $this->account );
+               return $api;
+       }
 }
diff --git a/PaymentProviders/Adyen/Tests/Data/auth.json 
b/PaymentProviders/Adyen/Tests/Data/auth.json
new file mode 100644
index 0000000..7e98f63
--- /dev/null
+++ b/PaymentProviders/Adyen/Tests/Data/auth.json
@@ -0,0 +1,24 @@
+{
+  "paymentMethod": "amex",
+  "operations": [
+    "CANCEL",
+    "CAPTURE",
+    "REFUND"
+  ],
+  "reason": "27533:0003:6\/2016",
+  "cvvResult": "1",
+  "avsResult": "7",
+  "parentPspReference": null,
+  "pspReference": "762895314225",
+  "merchantReference": "119223.0",
+  "merchantAccountCode": "WikimediaCOM",
+  "currency": "USD",
+  "amount": 10,
+  "eventDate": "2016-01-25T17:07:36.862+01:00",
+  "success": true,
+  "correlationId": "adyen-119223.0",
+  "propertiesExportedAsKeys": [
+    "correlationId"
+  ],
+  "propertiesExcludedFromExport": []
+}
diff --git a/PaymentProviders/Adyen/Tests/Data/pending.json 
b/PaymentProviders/Adyen/Tests/Data/pending.json
new file mode 100644
index 0000000..0a16bc9
--- /dev/null
+++ b/PaymentProviders/Adyen/Tests/Data/pending.json
@@ -0,0 +1,48 @@
+{
+  "city": "Columbus",
+  "city_2": "",
+  "comment": "",
+  "contribution_tracking_id": "119223",
+  "country": "US",
+  "country_2": "",
+  "currency": "USD",
+  "date": 1458060070,
+  "email": "[email protected]",
+  "fee": "0",
+  "first_name": "Testy119223",
+  "first_name_2": "",
+  "gateway": "adyen",
+  "gateway_account": "WikimediaCOM",
+  "gateway_txn_id": false,
+  "gross": "10.00",
+  "language": "en",
+  "last_name": "Testerson119223",
+  "last_name_2": "",
+  "middle_name": "",
+  "net": "",
+  "payment_method": "cc",
+  "payment_submethod": "amex",
+  "postal_code": "12345",
+  "postal_code_2": "",
+  "premium_language": "",
+  "recurring": "",
+  "referrer": "https:\/\/mediawiki.dev\/index.php\/Main_Page",
+  "response": false,
+  "risk_score": 10,
+  "size": "",
+  "state_province": "OH",
+  "state_province_2": "",
+  "street_address": "123 Fake St",
+  "street_address_2": "",
+  "supplemental_address_1": "",
+  "supplemental_address_2": "",
+  "user_ip": "127.0.0.1",
+  "utm_campaign": "",
+  "utm_medium": "",
+  "utm_source": "..cc",
+  "correlationId": "adyen-119223.0",
+  "propertiesExportedAsKeys": [
+    "correlationId"
+  ],
+  "propertiesExcludedFromExport": []
+}
diff --git a/PaymentProviders/Adyen/Tests/MockAdyenPaymentsAPI.php 
b/PaymentProviders/Adyen/Tests/MockAdyenPaymentsAPI.php
new file mode 100644
index 0000000..4c44a92
--- /dev/null
+++ b/PaymentProviders/Adyen/Tests/MockAdyenPaymentsAPI.php
@@ -0,0 +1,41 @@
+<?php namespace SmashPig\PaymentProviders\Adyen\Tests;
+
+use SmashPig\PaymentProviders\Adyen\AdyenPaymentsInterface;
+
+class MockAdyenPaymentsAPI implements AdyenPaymentsInterface {
+
+       protected $account = '';
+       protected $returnCode = false;
+
+       public function __construct( $returnCode ) {
+               $this->returnCode = $returnCode;
+       }
+
+       public function setAccount( $account ) {
+               $this->account = $account;
+       }
+
+       /**
+        * Fakes a Capture modification to a given Adyen transaction.
+        *
+        * @param string    $currency       Original currency of the request
+        * @param int       $amount         Amount to be captured. Less than or 
equal to the original request
+        * @param string    $pspReference   Original pspReference of the request
+        *
+        * @returns bool|string The return code set in the constructor.
+        */
+       public function capture( $currency, $amount, $pspReference ) {
+               return $this->returnCode;
+       }
+
+       /**
+        * Pretends to cancel an Adyen authorization
+        *
+        * @param string $pspReference Original pspReference of the request
+        *
+        * @returns bool|string The return code set in the constructor.
+        */
+       public function cancel( $pspReference ) {
+               return $this->returnCode;
+       }
+}
diff --git a/PaymentProviders/Adyen/Tests/config_test_failure.php 
b/PaymentProviders/Adyen/Tests/config_test_failure.php
new file mode 100644
index 0000000..47c32dc
--- /dev/null
+++ b/PaymentProviders/Adyen/Tests/config_test_failure.php
@@ -0,0 +1,24 @@
+<?php
+
+$config = array(
+       'adyen' => array(
+               'data-store' => array(
+                       'pending' => array(
+                               'class' => 'MockDataStore',
+                               'inst-args' => array(),
+                       ),
+                       'antifraud' => array(
+                               'class' => 'MockDataStore',
+                               'inst-args' => array(),
+                       ),
+               ),
+               'payment-provider' => array(
+                       'adyen' => array(
+                               'api' => array(
+                                       'class' => 
'SmashPig\PaymentProviders\Adyen\Tests\MockAdyenPaymentsAPI',
+                                       'inst-args' => array( false ),
+                               ),
+                       ),
+               )
+       )
+);
diff --git a/PaymentProviders/Adyen/Tests/config_test_success.php 
b/PaymentProviders/Adyen/Tests/config_test_success.php
new file mode 100644
index 0000000..efd7931
--- /dev/null
+++ b/PaymentProviders/Adyen/Tests/config_test_success.php
@@ -0,0 +1,24 @@
+<?php
+
+$config = array(
+       'adyen' => array(
+               'data-store' => array(
+                       'pending' => array(
+                               'class' => 'MockDataStore',
+                               'inst-args' => array(),
+                       ),
+                       'antifraud' => array(
+                               'class' => 'MockDataStore',
+                               'inst-args' => array(),
+                       ),
+               ),
+               'payment-provider' => array(
+                       'adyen' => array(
+                               'api' => array(
+                                       'class' => 
'SmashPig\PaymentProviders\Adyen\Tests\MockAdyenPaymentsAPI',
+                                       'inst-args' => array( 'Success!' ),
+                               ),
+                       ),
+               )
+       )
+);
diff --git a/PaymentProviders/Adyen/Tests/phpunit/CaptureJobTest.php 
b/PaymentProviders/Adyen/Tests/phpunit/CaptureJobTest.php
new file mode 100644
index 0000000..499ab9e
--- /dev/null
+++ b/PaymentProviders/Adyen/Tests/phpunit/CaptureJobTest.php
@@ -0,0 +1,43 @@
+<?php namespace SmashPig\PaymentProviders\Adyen\Test;
+
+use SmashPig\Core\Configuration;
+use SmashPig\Core\DataStores\KeyedOpaqueStorableObject;
+use SmashPig\PaymentProviders\Adyen\Jobs\ProcessCaptureRequestJob;
+
+/**
+ * Verify Adyen Capture job functions
+ */
+class CaptureJobTest extends \BaseSmashPigUnitTestCase {
+
+       /**
+        * For a legit donation, ProcessCaptureJob should leave donor data
+        * on the pending queue, add an antifraud message, and return true.
+        */
+       public function testSuccessfulCapture() {
+               $this->setConfig( __DIR__ . '/../config_test_success.php', 
'adyen' );
+               $antifraudQueue = Configuration::getDefaultConfig()->object( 
'data-store/antifraud', true );
+               $pendingQueue = Configuration::getDefaultConfig()->object( 
'data-store/pending', true );
+               $pendingQueue->addObject(
+                       KeyedOpaqueStorableObject::fromJsonProxy(
+                               
'SmashPig\CrmLink\Messages\DonationInterfaceMessage',
+                               file_get_contents( __DIR__ . 
'/../Data/pending.json' )
+                       )
+               );
+               $auth = KeyedOpaqueStorableObject::fromJsonProxy(
+                       
'SmashPig\PaymentProviders\Adyen\ExpatriatedMessages\Authorisation',
+                       file_get_contents( __DIR__ . '/../Data/auth.json' )
+               );
+               $job = ProcessCaptureRequestJob::factory( $auth );
+               $this->assertTrue( $job->execute() );
+               $this->assertNotNull(
+                       $pendingQueue->queueGetObject( null, 
$auth->correlationId ),
+                       'RequestCaptureJob did not leave donor data on pending 
queue'
+               );
+               $this->assertNotNull(
+                       // Blank correlation ID on antifraud messages
+                       $antifraudQueue->queueGetObject( null, "" ),
+                       'RequestCaptureJob did not send antifraud message'
+               );
+       }
+
+}
diff --git a/PaymentProviders/Amazon/Tests/phpunit/ApiTest.php 
b/PaymentProviders/Amazon/Tests/phpunit/ApiTest.php
index 9f908f1..1107383 100644
--- a/PaymentProviders/Amazon/Tests/phpunit/ApiTest.php
+++ b/PaymentProviders/Amazon/Tests/phpunit/ApiTest.php
@@ -10,13 +10,7 @@
 
        public function setUp() {
                parent::setUp();
-               $config = new Configuration(
-                       __DIR__ . '/../../../../config_defaults.php',
-                       __DIR__ . '/../config_test.php',
-                       'amazon',
-                       true
-               );
-               Context::init( $config );
+               $this->setConfig( __DIR__ . '/../config_test.php', 'amazon' );
                $this->mockClient = Context::get()->getConfiguration()->object( 
'payments-client', true );
                $this->mockClient->calls = array();
                $this->mockClient->returns = array();
diff --git a/Tests/BaseSmashPigUnitTestCase.php 
b/Tests/BaseSmashPigUnitTestCase.php
index e25fd43..8bb5b3e 100644
--- a/Tests/BaseSmashPigUnitTestCase.php
+++ b/Tests/BaseSmashPigUnitTestCase.php
@@ -1,13 +1,38 @@
 <?php
+use SmashPig\Core\Context;
+use SmashPig\Core\Configuration;
+use SmashPig\Core\Logging\Logger;
 
 class BaseSmashPigUnitTestCase extends PHPUnit_Framework_TestCase {
+       protected static $loggerCreated = false; // Aaargh!
+
        function setUp() {
                parent::setUp();
 
                require_once __DIR__ . '/../vendor/autoload.php';
        }
 
+       function tearDown() {
+               Context::set(); // Nullify the context for next run.
+       }
+
        function loadJson( $path ) {
                return json_decode( file_get_contents( $path ), true );
        }
+
+       function setConfig( $configPath = null, $configNode = 'default' ) {
+               $defaultConfig = __DIR__ . '/../config_defaults.php';
+               $config = new Configuration(
+                       $defaultConfig,
+                       $configPath,
+                       $configNode,
+                       true
+               );
+               Context::init( $config );
+               if ( !self::$loggerCreated ) {
+                       // Don't care which config the logger gets, let's just 
not explode
+                       Logger::init( 'test', 'debug', $config );
+                       self::$loggerCreated = true;
+               }
+       }
 }
diff --git a/Tests/MockDataStore.php b/Tests/MockDataStore.php
new file mode 100644
index 0000000..49be5cc
--- /dev/null
+++ b/Tests/MockDataStore.php
@@ -0,0 +1,116 @@
+<?php
+use SmashPig\Core\DataStores\DataStoreException;
+use SmashPig\Core\DataStores\DataStoreTransactionException;
+use SmashPig\Core\DataStores\KeyedOpaqueDataStore;
+use SmashPig\Core\DataStores\KeyedOpaqueStorableObject;
+use SmashPig\Core\SmashPigException;
+
+/**
+ * Fakes some data store behavior for tests
+ * TODO: anything that cares about message type
+ * Class MockDataStore
+ */
+class MockDataStore extends KeyedOpaqueDataStore {
+
+       protected $messages = array();
+
+       protected $currentQueue = null;
+
+       public function __construct() {}
+
+       /**
+        * Adds an object to the persistent data store.
+        *
+        * @param \SmashPig\Core\DataStores\KeyedOpaqueStorableObject $obj
+        *
+        * @throws \SmashPig\Core\DataStores\DataStoreException if the message 
could not be stored.
+        * @return null
+        */
+       public function addObject( KeyedOpaqueStorableObject $obj ) {
+               $keys = $obj->getObjectKeys();
+               if ( !array_key_exists( 'correlationId', $keys ) ) {
+                       throw new DataStoreException(
+                               "Required property correlationId was not 
exposed."
+                       );
+               }
+               $corrId = $keys['correlationId'];
+               if ( !isset( $this->messages[$corrId] ) ) {
+                       $this->messages[$corrId] = array();
+               }
+               array_push( $this->messages[$corrId], $obj );
+       }
+
+       /**
+        * Remove objects with the same serialization type and correlation ID 
from the
+        * persistent store.
+        * @param \SmashPig\Core\DataStores\KeyedOpaqueStorableObject $protoObj 
Prototype to remove.
+        *
+        * @return int Count of messages removed.
+        */
+       public function removeObjects( KeyedOpaqueStorableObject $protoObj ) {
+               // TODO
+       }
+
+       /**
+        * Remove objects with a given correlation ID from the store.
+        *
+        * @param string $id Correlation ID of messages to remove
+        *
+        * @return int Count of messages removed.
+        */
+       public function removeObjectsById( $id ) {
+               if ( isset( $this->messages[$id] ) ) {
+                       unset( $this->messages[$id] );
+               }
+       }
+
+       /**
+        *
+        * If a object has not yet been completely acked when this function 
gets called,
+        * it will throw a DataStoreTransactionException exception.
+        *
+        * If there were no objects fitting the filter, null will be returned.
+        *
+        * @param string|null $type ignored
+        * @param null|string $id The correlation ID of the message
+        *
+        * @throws \SmashPig\Core\DataStores\DataStoreTransactionException
+        * @return KeyedOpaqueStorableObject|null
+        */
+       public function queueGetObject( $type = null, $id = null ) {
+               if ( $id === null ) {
+                       throw new SmashPigException( 'Need id for mock queue' );
+               }
+               if ( empty( $this->messages[$id] ) ) {
+                       return null;
+               }
+               $this->currentQueue = & $this->messages[$id];
+               return $this->currentQueue[0];
+       }
+
+       /**
+        * Acknowledges and removes from the backing data store the current 
queue object
+        */
+       public function queueAckObject() {
+               if ( !$this->currentQueue ) {
+                       throw new DataStoreTransactionException(
+                               "No STOMP transaction currently in progress. 
Cannot ACK a non-existent message!"
+                       );
+               }
+               array_shift( $this->currentQueue );
+       }
+
+       /**
+        * Acknowledges and replaces into the backing data store the current 
queue object
+        */
+       public function queueIgnoreObject() {
+               if ( !$this->currentQueue ) {
+                       throw new DataStoreTransactionException(
+                               "No STOMP transaction currently in progress. 
Cannot ACK a non-existent message!"
+                       );
+               }
+               $currentMessage = $this->currentQueue[0];
+               $this->queueAckObject();
+               $this->addObject( $currentMessage );
+       }
+}
diff --git a/Tests/bootstrap-phpunit.php b/Tests/bootstrap-phpunit.php
index ed0032a..386c9f9 100644
--- a/Tests/bootstrap-phpunit.php
+++ b/Tests/bootstrap-phpunit.php
@@ -2,3 +2,4 @@
 
 require_once( 'vendor/autoload.php' );
 require_once( 'Tests/BaseSmashPigUnitTestCase.php' );
+require_once( 'Tests/MockDataStore.php' );
diff --git a/config_defaults.php b/config_defaults.php
index d21c326..5bbf536 100644
--- a/config_defaults.php
+++ b/config_defaults.php
@@ -117,6 +117,11 @@
 
                'payment-provider' => array(
                        'adyen' => array(
+                               'api' => array(
+                                       'class' => 
'SmashPig\PaymentProviders\Adyen\AdyenPaymentsAPI',
+                                       'inst-args' => array(),
+                               ),
+
                                'payments-wsdl' => 
'https://pal-live.adyen.com/pal/Payment.wsdl',
 
                                'accounts' => array(

-- 
To view, visit https://gerrit.wikimedia.org/r/277554
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I4aae0d4fce2275920597867bf2e56d569ce6738d
Gerrit-PatchSet: 8
Gerrit-Project: wikimedia/fundraising/SmashPig
Gerrit-Branch: master
Gerrit-Owner: Ejegg <[email protected]>
Gerrit-Reviewer: Awight <[email protected]>
Gerrit-Reviewer: Cdentinger <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to