From 717873bd4a08337cc7a7e40af4d4dfaf303c09fc Mon Sep 17 00:00:00 2001 From: Yurii Torbyk Date: Fri, 8 Jul 2016 15:15:44 +0300 Subject: [PATCH] Added unit tests. Code improvements. --- composer.json | 7 +- .../Exception/EntityInvalidDataException.php | 7 + src/SubscribePro/Exception/HttpException.php | 45 + src/SubscribePro/Http.php | 58 +- src/SubscribePro/Sdk.php | 150 +-- src/SubscribePro/Service/AbstractService.php | 46 +- .../Service/AbstractServiceFactory.php | 51 + .../Service/Address/AddressService.php | 28 +- .../Service/Address/AddressServiceFactory.php | 30 + .../Service/Customer/CustomerService.php | 26 +- .../Customer/CustomerServiceFactory.php | 30 + .../Service/PaymentProfile/PaymentProfile.php | 3 +- .../PaymentProfile/PaymentProfileService.php | 37 +- .../PaymentProfileServiceFactory.php | 32 + .../Service/Product/ProductService.php | 25 +- .../Service/Product/ProductServiceFactory.php | 30 + .../Service/ServiceFactoryInterface.php | 11 + .../Service/ServiceFactoryResolver.php | 87 ++ .../Subscription/SubscriptionService.php | 27 +- .../SubscriptionServiceFactory.php | 34 + .../Service/Token/TokenService.php | 25 +- .../Service/Token/TokenServiceFactory.php | 30 + .../Transaction/TransactionService.php | 53 +- .../Transaction/TransactionServiceFactory.php | 30 + .../Service/Webhook/WebhookService.php | 36 +- .../Service/Webhook/WebhookServiceFactory.php | 41 + src/SubscribePro/Tools/AbstractTool.php | 19 + src/SubscribePro/Tools/Config.php | 17 +- src/SubscribePro/Tools/Report.php | 27 +- src/SubscribePro/Tools/ToolFactory.php | 51 + src/SubscribePro/Utils/StringUtils.php | 24 + tests/AppTest.php | 2 +- tests/HttpTest.php | 431 ++++++++ tests/Service/Address/AddressServiceTest.php | 231 +++++ tests/Service/Address/AddressTest.php | 273 +++++ .../Service/Customer/CustomerServiceTest.php | 206 ++++ tests/Service/Customer/CustomerTest.php | 151 +++ .../PaymentProfileServiceTest.php | 366 +++++++ .../PaymentProfile/PaymentProfileTest.php | 949 ++++++++++++++++++ tests/Service/Product/ProductServiceTest.php | 190 ++++ tests/Service/Product/ProductTest.php | 192 ++++ .../Subscription/SubscriptionServiceTest.php | 232 +++++ .../Service/Subscription/SubscriptionTest.php | 654 ++++++++++++ tests/Service/Token/TokenServiceTest.php | 119 +++ tests/Service/Token/TokenTest.php | 165 +++ .../Transaction/TransactionServiceTest.php | 598 +++++++++++ tests/Service/Transaction/TransactionTest.php | 384 +++++++ .../Webhook/Destination/DestinationTest.php | 29 + tests/Service/Webhook/EventTest.php | 65 ++ tests/Service/Webhook/WebhookServiceTest.php | 76 ++ tests/Tools/ConfigTest.php | 60 ++ tests/Tools/ReportTest.php | 109 ++ 52 files changed, 6256 insertions(+), 343 deletions(-) create mode 100644 src/SubscribePro/Exception/EntityInvalidDataException.php create mode 100644 src/SubscribePro/Service/AbstractServiceFactory.php create mode 100644 src/SubscribePro/Service/Address/AddressServiceFactory.php create mode 100644 src/SubscribePro/Service/Customer/CustomerServiceFactory.php create mode 100644 src/SubscribePro/Service/PaymentProfile/PaymentProfileServiceFactory.php create mode 100644 src/SubscribePro/Service/Product/ProductServiceFactory.php create mode 100644 src/SubscribePro/Service/ServiceFactoryInterface.php create mode 100644 src/SubscribePro/Service/ServiceFactoryResolver.php create mode 100644 src/SubscribePro/Service/Subscription/SubscriptionServiceFactory.php create mode 100644 src/SubscribePro/Service/Token/TokenServiceFactory.php create mode 100644 src/SubscribePro/Service/Transaction/TransactionServiceFactory.php create mode 100644 src/SubscribePro/Service/Webhook/WebhookServiceFactory.php create mode 100644 src/SubscribePro/Tools/AbstractTool.php create mode 100644 src/SubscribePro/Tools/ToolFactory.php create mode 100644 src/SubscribePro/Utils/StringUtils.php create mode 100644 tests/HttpTest.php create mode 100644 tests/Service/Address/AddressServiceTest.php create mode 100644 tests/Service/Address/AddressTest.php create mode 100644 tests/Service/Customer/CustomerServiceTest.php create mode 100644 tests/Service/Customer/CustomerTest.php create mode 100644 tests/Service/PaymentProfile/PaymentProfileServiceTest.php create mode 100644 tests/Service/PaymentProfile/PaymentProfileTest.php create mode 100644 tests/Service/Product/ProductServiceTest.php create mode 100644 tests/Service/Product/ProductTest.php create mode 100644 tests/Service/Subscription/SubscriptionServiceTest.php create mode 100644 tests/Service/Subscription/SubscriptionTest.php create mode 100644 tests/Service/Token/TokenServiceTest.php create mode 100644 tests/Service/Token/TokenTest.php create mode 100644 tests/Service/Transaction/TransactionServiceTest.php create mode 100644 tests/Service/Transaction/TransactionTest.php create mode 100644 tests/Service/Webhook/Destination/DestinationTest.php create mode 100644 tests/Service/Webhook/EventTest.php create mode 100644 tests/Service/Webhook/WebhookServiceTest.php create mode 100644 tests/Tools/ConfigTest.php create mode 100644 tests/Tools/ReportTest.php diff --git a/composer.json b/composer.json index 32e93e2..9b8db13 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "license": "MIT", "authors": [ { - "name": "Swarming Tech", + "name": "Swarming Technology", "homepage": "http://swarmingtech.com" }, { @@ -20,11 +20,10 @@ }, "require": { "php": ">=5.5", - "guzzlehttp/guzzle": "~6.0.1|~6.1|~6.2", - "monolog/monolog": "1.16.0" + "guzzlehttp/guzzle": "~6.0.1|~6.1|~6.2" }, "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" + "monolog/monolog": "^1.16.0" }, "autoload": { "psr-4": { diff --git a/src/SubscribePro/Exception/EntityInvalidDataException.php b/src/SubscribePro/Exception/EntityInvalidDataException.php new file mode 100644 index 0000000..c1416bf --- /dev/null +++ b/src/SubscribePro/Exception/EntityInvalidDataException.php @@ -0,0 +1,7 @@ +getErrorMessage($response), $code, $previous); + + $this->response = $response; + } + + /** + * @return int + */ + public function getStatusCode() + { + return $this->response->getStatusCode(); + } + + /** + * @return \Psr\Http\Message\ResponseInterface + */ + public function getResponse() + { + return $this->response; + } + + /** + * @param \Psr\Http\Message\ResponseInterface $response + * @return string + */ + protected function getErrorMessage(ResponseInterface $response) + { + $errorBody = json_decode((string)$response->getBody(), true); + return !empty($errorBody['message']) ? $errorBody['message'] : $response->getReasonPhrase(); + } } diff --git a/src/SubscribePro/Http.php b/src/SubscribePro/Http.php index 0b0d4b2..3318fc9 100644 --- a/src/SubscribePro/Http.php +++ b/src/SubscribePro/Http.php @@ -56,12 +56,15 @@ class Http public function __construct($app) { $this->app = $app; - $this->handlerStack = HandlerStack::create(); + $this->handlerStack = $this->createHandlerStack(); } - protected function initClient() + /** + * @return \GuzzleHttp\Client + */ + protected function createClient() { - $this->client = new Client([ + return new Client([ 'base_uri' => $this->baseUrl, 'handler' => $this->handlerStack, RequestOptions::HTTP_ERRORS => false, @@ -69,6 +72,14 @@ protected function initClient() ]); } + /** + * @return \GuzzleHttp\HandlerStack + */ + protected function createHandlerStack() + { + return HandlerStack::create(); + } + /** * @return \GuzzleHttp\HandlerStack */ @@ -81,14 +92,15 @@ public function getHandlerStack() * @param string|null $fileName * @param string|null $lineFormat * @param string|null $messageFormat - * @param string $logLevel + * @param string|null $logLevel * @return Http */ - public function initDefaultLogger($fileName = null, $lineFormat = null, $messageFormat = null, $logLevel = LogLevel::INFO) + public function initDefaultLogger($fileName = null, $lineFormat = null, $messageFormat = null, $logLevel = null) { $fileName = $fileName ?: static::DEFAULT_LOG_FILE_NAME; $lineFormat = $lineFormat ?: static::DEFAULT_LOG_LINE_FORMAT; $messageFormat = $messageFormat ?: static::DEFAULT_LOG_MESSAGE_FORMAT; + $logLevel = $logLevel ?: LogLevel::INFO; $logHandler = new RotatingFileHandler($fileName); $logHandler->setFormatter(new LineFormatter($lineFormat, null, true)); @@ -104,18 +116,32 @@ public function initDefaultLogger($fileName = null, $lineFormat = null, $message */ public function addLogger($logger, $messageFormatter, $logLevel = LogLevel::INFO) { - $this->handlerStack->push(Middleware::log($logger, $messageFormatter, $logLevel), 'logger'); + $this->getHandlerStack() + ->push( + $this->createMiddlewareLogCallback($logger, $messageFormatter, $logLevel), + 'logger' + ); return $this; } /** - * @param bool $force + * @param \Psr\Log\LoggerInterface $logger + * @param \GuzzleHttp\MessageFormatter $messageFormatter + * @param string $logLevel + * @return callable + */ + protected function createMiddlewareLogCallback($logger, $messageFormatter, $logLevel = LogLevel::INFO) + { + return Middleware::log($logger, $messageFormatter, $logLevel); + } + + /** * @return \GuzzleHttp\Client */ - public function getClient($force = false) + protected function getClient() { - if (null === $this->client || $force) { - $this->initClient(); + if (null === $this->client) { + $this->client = $this->createClient(); } return $this->client; } @@ -187,16 +213,6 @@ protected function processResponse($response) return !empty($body) ? json_decode($body, true) : $response->getStatusCode(); } - throw new HttpException($this->getErrorMessage($response), $response->getStatusCode()); - } - - /** - * @param \Psr\Http\Message\ResponseInterface $response - * @return string - */ - protected function getErrorMessage($response) - { - $errorBody = json_decode((string)$response->getBody(), true); - return !empty($errorBody['message']) ? $errorBody['message'] : $response->getReasonPhrase(); + throw new HttpException($response); } } diff --git a/src/SubscribePro/Sdk.php b/src/SubscribePro/Sdk.php index e72ca27..4dd4f20 100644 --- a/src/SubscribePro/Sdk.php +++ b/src/SubscribePro/Sdk.php @@ -4,7 +4,9 @@ use SubscribePro\Exception\InvalidArgumentException; use SubscribePro\Exception\BadMethodCallException; -use Psr\Log\LogLevel; +use SubscribePro\Service\ServiceFactoryResolver; +use SubscribePro\Tools\ToolFactory; +use SubscribePro\Utils\StringUtils; /** * @method \SubscribePro\Service\Product\ProductService getProductService() @@ -20,13 +22,8 @@ */ class Sdk { - /** - * Version SDK - * - * @const string - */ - const VERSION = '0.1.0'; - + use StringUtils; + /** * The name of the environment variable that contains the client ID * @@ -42,19 +39,14 @@ class Sdk const CLIENT_SECRET_ENV_NAME = 'SUBSCRIBEPRO_CLIENT_SECRET'; /** - * @var \SubscribePro\App - */ - protected $app; - - /** - * @var \SubscribePro\Http + * @var \SubscribePro\Service\ServiceFactoryResolver */ - protected $http; + protected $serviceFactoryResolver; /** - * @var array + * @var \SubscribePro\Tools\ToolFactory */ - protected $config; + protected $toolFactory; /** * @var array @@ -67,6 +59,22 @@ class Sdk protected $tools = []; /** + * Config options: + * - client_id + * - client_secret + * - logging_enable + * default value false + * - logging_level + * default value @see \Psr\Log\LogLevel::INFO + * - logging_file_name + * default value @see SubscribePro\Http::DEFAULT_LOG_FILE_NAME + * - logging_line_format + * default value @see SubscribePro\Http::DEFAULT_LOG_LINE_FORMAT + * - logging_message_format + * default value @see SubscribePro\Http::DEFAULT_LOG_MESSAGE_FORMAT + * - + * Config options for specified service + * * @param array $config * @throws \SubscribePro\Exception\InvalidArgumentException */ @@ -76,9 +84,9 @@ public function __construct(array $config = []) 'client_id' => getenv(static::CLIENT_ID_ENV_NAME), 'client_secret' => getenv(static::CLIENT_SECRET_ENV_NAME), 'logging_enable' => false, - 'logging_level' => LogLevel::INFO, + 'logging_level' => null, 'logging_file_name' => null, - 'logging_file_format' => null, + 'logging_line_format' => null, 'logging_message_format' => null ], $config); @@ -89,45 +97,28 @@ public function __construct(array $config = []) throw new InvalidArgumentException('Required "client_secret" key is not supplied in config and could not find fallback environment variable "' . static::CLIENT_SECRET_ENV_NAME . '"'); } - $this->app = new App($config['client_id'], $config['client_secret']); + $app = new App($config['client_id'], $config['client_secret']); unset($config['client_id']); unset($config['client_secret']); - $this->http = new Http($this->app); + $http = new Http($app); if ($config['logging_enable']) { - $this->http->initDefaultLogger( + $http->initDefaultLogger( $config['logging_file_name'], - $config['logging_file_format'], + $config['logging_line_format'], $config['logging_message_format'], $config['logging_level'] ); - unset($config['logging_enable']); - unset($config['logging_level']); - unset($config['logging_file_name']); - unset($config['logging_file_format']); - unset($config['logging_message_format']); } - - $this->config = $config; - } - - /** - * @return \SubscribePro\Http - */ - public function getHttp() - { - return $this->http; - } - - /** - * @param string $name - * @return array - */ - protected function getServiceConfig($name) - { - $name = $this->underscore($name); - return (array)(empty($this->config[$name]) ? [] : $this->config[$name]); + unset($config['logging_enable']); + unset($config['logging_level']); + unset($config['logging_file_name']); + unset($config['logging_line_format']); + unset($config['logging_message_format']); + + $this->serviceFactoryResolver = new ServiceFactoryResolver($http, $config); + $this->toolFactory = new ToolFactory($http); } /** @@ -146,48 +137,20 @@ public function getService($name) } /** - * Create service by name - * * @param string $name * @return \SubscribePro\Service\AbstractService * @throws \SubscribePro\Exception\InvalidArgumentException */ - protected function createService($name) - { - $name = $this->camelize($name); - $serviceClient = "SubscribePro\\Service\\{$name}\\{$name}Service"; - - if (!class_exists($serviceClient)) { - throw new InvalidArgumentException("Service with '{$name}' name does not exist."); - } - - return new $serviceClient($this, $this->getServiceConfig($name)); - } - - /** - * Create tool by name - * - * @param string $name - * @return mixed - * @throws \SubscribePro\Exception\InvalidArgumentException - */ - protected function createTool($name) + private function createService($name) { - $name = $this->camelize($name); - $tool = "SubscribePro\\Tools\\{$name}"; - - if (!class_exists($tool)) { - throw new InvalidArgumentException("Tool with '{$name}' name does not exist."); - } - - return new $tool($this); + return $this->serviceFactoryResolver->getServiceFactory($name)->create(); } /** * Get tool by name * * @param string $name - * @return mixed + * @return \SubscribePro\Tools\AbstractTool * @throws \SubscribePro\Exception\InvalidArgumentException */ public function getTool($name) @@ -198,10 +161,19 @@ public function getTool($name) return $this->tools[$name]; } + /** + * @param $name + * @return \SubscribePro\Tools\AbstractTool + */ + private function createTool($name) + { + return $this->toolFactory->create($name); + } + /** * @param string $method * @param array $args - * @return mixed + * @return \SubscribePro\Service\AbstractService|\SubscribePro\Tools\AbstractTool * @throws \BadMethodCallException */ public function __call($method, $args) @@ -216,22 +188,4 @@ public function __call($method, $args) throw new BadMethodCallException("Method {$method} does not exist."); } - - /** - * @param string $name - * @return string - */ - protected function camelize($name) - { - return implode('', array_map('ucfirst', explode('_', $name))); - } - - /** - * @param string $name - * @return string - */ - protected function underscore($name) - { - return strtolower(trim(preg_replace('/([A-Z]|[0-9]+)/', '_$1', $name), '_')); - } } diff --git a/src/SubscribePro/Service/AbstractService.php b/src/SubscribePro/Service/AbstractService.php index 3e634e2..d518136 100644 --- a/src/SubscribePro/Service/AbstractService.php +++ b/src/SubscribePro/Service/AbstractService.php @@ -2,7 +2,7 @@ namespace SubscribePro\Service; -use SubscribePro\Sdk; +use SubscribePro\Http; abstract class AbstractService { @@ -10,7 +10,7 @@ abstract class AbstractService * Config instance name */ const CONFIG_INSTANCE_NAME = 'instance_name'; - + /** * @var \SubscribePro\Http */ @@ -22,43 +22,13 @@ abstract class AbstractService protected $dataFactory; /** - * @var array - */ - protected $config; - - /** - * @param \SubscribePro\Sdk $sdk - * @param array $config - */ - public function __construct(Sdk $sdk, array $config = []) - { - $this->httpClient = $sdk->getHttp(); - $this->config = $config; - $this->dataFactory = $this->createDataFactory($sdk); - } - - /** - * @param string $key - * @param mixed|null $defaultValue - * @return mixed|null - */ - protected function getConfigValue($key, $defaultValue = null) - { - return isset($this->config[$key]) ? $this->config[$key] : $defaultValue; - } - - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - abstract protected function createDataFactory(Sdk $sdk); - - /** - * @return \SubscribePro\Service\DataFactoryInterface + * @param Http $http + * @param DataFactoryInterface $factory */ - protected function getDataFactory() + public function __construct(Http $http, DataFactoryInterface $factory) { - return $this->dataFactory; + $this->httpClient = $http; + $this->dataFactory = $factory; } /** @@ -90,7 +60,7 @@ protected function retrieveItems($response, $entitiesName) * @param array $data * @return \SubscribePro\Service\DataInterface[] */ - protected function createItems(array $data = []) + private function createItems(array $data = []) { return array_map(function ($itemData) { return $this->dataFactory->create($itemData); diff --git a/src/SubscribePro/Service/AbstractServiceFactory.php b/src/SubscribePro/Service/AbstractServiceFactory.php new file mode 100644 index 0000000..2db8e23 --- /dev/null +++ b/src/SubscribePro/Service/AbstractServiceFactory.php @@ -0,0 +1,51 @@ +serviceFactoryResolver = $serviceFactoryResolver; + $this->httpClient = $httpClient; + $this->config = $config; + } + + /** + * @param string $key + * @param mixed|null $defaultValue + * @return mixed|null + */ + protected function getConfigValue($key, $defaultValue = null) + { + return isset($this->config[$key]) ? $this->config[$key] : $defaultValue; + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + abstract protected function createDataFactory(); +} diff --git a/src/SubscribePro/Service/Address/AddressService.php b/src/SubscribePro/Service/Address/AddressService.php index 277ba3d..01d6846 100644 --- a/src/SubscribePro/Service/Address/AddressService.php +++ b/src/SubscribePro/Service/Address/AddressService.php @@ -2,10 +2,16 @@ namespace SubscribePro\Service\Address; -use SubscribePro\Sdk; +use SubscribePro\Exception\EntityInvalidDataException; use SubscribePro\Service\AbstractService; -use SubscribePro\Exception\InvalidArgumentException; +/** + * Config options for address service: + * - instance_name + * Specified class must implement \SubscribePro\Service\Address\AddressInterface interface + * Default value is \SubscribePro\Service\Address\Address + * @see \SubscribePro\Service\Address\AddressInterface + */ class AddressService extends AbstractService { /** @@ -16,17 +22,6 @@ class AddressService extends AbstractService const API_NAME_ADDRESS = 'address'; const API_NAME_ADDRESSES = 'addresses'; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - return new AddressFactory( - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Address\Address') - ); - } - /** * @param array $addressData * @return \SubscribePro\Service\Address\AddressInterface @@ -50,12 +45,13 @@ public function loadAddress($addressId) /** * @param \SubscribePro\Service\Address\AddressInterface $address * @return \SubscribePro\Service\Address\AddressInterface + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveAddress(AddressInterface $address) { if (!$address->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $url = $address->isNew() ? '/services/v2/address.json' : "/services/v2/addresses/{$address->getId()}.json"; @@ -66,13 +62,13 @@ public function saveAddress(AddressInterface $address) /** * @param \SubscribePro\Service\Address\AddressInterface $address * @return \SubscribePro\Service\Address\AddressInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function findOrSave($address) { if (!$address->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post('/services/v2/address/find-or-create.json', [self::API_NAME_ADDRESS => $address->getFormData()]); diff --git a/src/SubscribePro/Service/Address/AddressServiceFactory.php b/src/SubscribePro/Service/Address/AddressServiceFactory.php new file mode 100644 index 0000000..44d5b34 --- /dev/null +++ b/src/SubscribePro/Service/Address/AddressServiceFactory.php @@ -0,0 +1,30 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + return new AddressFactory( + $this->getConfigValue(AddressService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Address\Address') + ); + } +} diff --git a/src/SubscribePro/Service/Customer/CustomerService.php b/src/SubscribePro/Service/Customer/CustomerService.php index 8132d1c..444b97b 100644 --- a/src/SubscribePro/Service/Customer/CustomerService.php +++ b/src/SubscribePro/Service/Customer/CustomerService.php @@ -2,10 +2,17 @@ namespace SubscribePro\Service\Customer; -use SubscribePro\Sdk; +use SubscribePro\Exception\EntityInvalidDataException; use SubscribePro\Service\AbstractService; use SubscribePro\Exception\InvalidArgumentException; +/** + * Config options for customer service: + * - instance_name + * Specified class must implement \SubscribePro\Service\Customer\CustomerInterface interface + * Default value is \SubscribePro\Service\Customer\Customer + * @see \SubscribePro\Service\Customer\CustomerInterface + */ class CustomerService extends AbstractService { /** @@ -26,17 +33,6 @@ class CustomerService extends AbstractService CustomerInterface::LAST_NAME ]; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - return new CustomerFactory( - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Customer\Customer') - ); - } - /** * @param array $customerData * @return \SubscribePro\Service\Customer\CustomerInterface @@ -49,13 +45,13 @@ public function createCustomer(array $customerData = []) /** * @param \SubscribePro\Service\Customer\CustomerInterface $customer * @return \SubscribePro\Service\Customer\CustomerInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveCustomer(CustomerInterface $customer) { if (!$customer->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $url = $customer->isNew() ? '/services/v2/customer.json' : "/services/v2/customers/{$customer->getId()}.json"; @@ -82,7 +78,7 @@ public function loadCustomer($customerId) * - first_name * - last_name * - * @param array|null $filters + * @param array $filters * @return \SubscribePro\Service\Customer\CustomerInterface[] * @throws \SubscribePro\Exception\InvalidArgumentException * @throws \SubscribePro\Exception\HttpException diff --git a/src/SubscribePro/Service/Customer/CustomerServiceFactory.php b/src/SubscribePro/Service/Customer/CustomerServiceFactory.php new file mode 100644 index 0000000..25ff051 --- /dev/null +++ b/src/SubscribePro/Service/Customer/CustomerServiceFactory.php @@ -0,0 +1,30 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + return new CustomerFactory( + $this->getConfigValue(CustomerService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Customer\Customer') + ); + } +} diff --git a/src/SubscribePro/Service/PaymentProfile/PaymentProfile.php b/src/SubscribePro/Service/PaymentProfile/PaymentProfile.php index 40f8fbf..8fa6723 100644 --- a/src/SubscribePro/Service/PaymentProfile/PaymentProfile.php +++ b/src/SubscribePro/Service/PaymentProfile/PaymentProfile.php @@ -158,7 +158,8 @@ public function getThirdPartyTokenFormData() */ public function isThirdPartyDataValid() { - return $this->checkRequiredFields($this->savingThirdPartyTokenFields); + return $this->checkRequiredFields($this->savingThirdPartyTokenFields) + && $this->getBillingAddress()->isAsChildValid(true); } /** diff --git a/src/SubscribePro/Service/PaymentProfile/PaymentProfileService.php b/src/SubscribePro/Service/PaymentProfile/PaymentProfileService.php index a8c1146..593089a 100644 --- a/src/SubscribePro/Service/PaymentProfile/PaymentProfileService.php +++ b/src/SubscribePro/Service/PaymentProfile/PaymentProfileService.php @@ -2,10 +2,17 @@ namespace SubscribePro\Service\PaymentProfile; -use SubscribePro\Sdk; +use SubscribePro\Exception\EntityInvalidDataException; use SubscribePro\Service\AbstractService; use SubscribePro\Exception\InvalidArgumentException; +/** + * Config options for payment profile service: + * - instance_name + * Specified class must implement \SubscribePro\Service\PaymentProfile\PaymentProfileInterface interface + * Default value is \SubscribePro\Service\PaymentProfile\PaymentProfile + * @see \SubscribePro\Service\PaymentProfile\PaymentProfileInterface + */ class PaymentProfileService extends AbstractService { /** @@ -24,18 +31,6 @@ class PaymentProfileService extends AbstractService PaymentProfileInterface::CUSTOMER_EMAIL ]; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - return new PaymentProfileFactory( - $sdk->getAddressService()->getDataFactory(), - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\PaymentProfile\PaymentProfile') - ); - } - /** * @param array $paymentProfileData * @return \SubscribePro\Service\PaymentProfile\PaymentProfileInterface @@ -48,13 +43,13 @@ public function createProfile(array $paymentProfileData = []) /** * @param \SubscribePro\Service\PaymentProfile\PaymentProfileInterface $paymentProfile * @return \SubscribePro\Service\PaymentProfile\PaymentProfileInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveProfile(PaymentProfileInterface $paymentProfile) { if (!$paymentProfile->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $postData = [self::API_NAME_PROFILE => $paymentProfile->getFormData()]; @@ -112,13 +107,13 @@ public function loadProfiles(array $filters = []) /** * @param \SubscribePro\Service\PaymentProfile\PaymentProfileInterface $paymentProfile * @return \SubscribePro\Service\PaymentProfile\PaymentProfileInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveThirdPartyToken(PaymentProfileInterface $paymentProfile) { if (!$paymentProfile->isThirdPartyDataValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post( @@ -132,13 +127,13 @@ public function saveThirdPartyToken(PaymentProfileInterface $paymentProfile) * @param string $token * @param \SubscribePro\Service\PaymentProfile\PaymentProfileInterface $paymentProfile * @return \SubscribePro\Service\PaymentProfile\PaymentProfileInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveToken($token, PaymentProfileInterface $paymentProfile) { if (!$paymentProfile->isTokenDataValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post( @@ -152,13 +147,13 @@ public function saveToken($token, PaymentProfileInterface $paymentProfile) * @param string $token * @param \SubscribePro\Service\PaymentProfile\PaymentProfileInterface $paymentProfile * @return \SubscribePro\Service\PaymentProfile\PaymentProfileInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function verifyAndSaveToken($token, PaymentProfileInterface $paymentProfile) { if (!$paymentProfile->isTokenDataValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post( diff --git a/src/SubscribePro/Service/PaymentProfile/PaymentProfileServiceFactory.php b/src/SubscribePro/Service/PaymentProfile/PaymentProfileServiceFactory.php new file mode 100644 index 0000000..c1bddff --- /dev/null +++ b/src/SubscribePro/Service/PaymentProfile/PaymentProfileServiceFactory.php @@ -0,0 +1,32 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + return new PaymentProfileFactory( + $this->serviceFactoryResolver->getServiceFactory(AddressService::NAME)->createDataFactory(), + $this->getConfigValue(PaymentProfileService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\PaymentProfile\PaymentProfile') + ); + } +} diff --git a/src/SubscribePro/Service/Product/ProductService.php b/src/SubscribePro/Service/Product/ProductService.php index ad58158..dc671ab 100644 --- a/src/SubscribePro/Service/Product/ProductService.php +++ b/src/SubscribePro/Service/Product/ProductService.php @@ -2,10 +2,16 @@ namespace SubscribePro\Service\Product; -use SubscribePro\Sdk; +use SubscribePro\Exception\EntityInvalidDataException; use SubscribePro\Service\AbstractService; -use SubscribePro\Exception\InvalidArgumentException; +/** + * Config options for product service: + * - instance_name + * Specified class must implement \SubscribePro\Service\Product\ProductInterface interface + * Default value is \SubscribePro\Service\Product\Product + * @see \SubscribePro\Service\Product\ProductInterface + */ class ProductService extends AbstractService { /** @@ -16,17 +22,6 @@ class ProductService extends AbstractService const API_NAME_PRODUCT = 'product'; const API_NAME_PRODUCTS = 'products'; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - return new ProductFactory( - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Product\Product') - ); - } - /** * @param array $productData * @return \SubscribePro\Service\Product\ProductInterface @@ -39,13 +34,13 @@ public function createProduct(array $productData = []) /** * @param \SubscribePro\Service\Product\ProductInterface $product * @return \SubscribePro\Service\Product\ProductInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveProduct(ProductInterface $product) { if (!$product->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $url = $product->isNew() ? '/services/v2/product.json' : "/services/v2/products/{$product->getId()}.json"; diff --git a/src/SubscribePro/Service/Product/ProductServiceFactory.php b/src/SubscribePro/Service/Product/ProductServiceFactory.php new file mode 100644 index 0000000..2e66824 --- /dev/null +++ b/src/SubscribePro/Service/Product/ProductServiceFactory.php @@ -0,0 +1,30 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + return new ProductFactory( + $this->getConfigValue(ProductService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Product\Product') + ); + } +} diff --git a/src/SubscribePro/Service/ServiceFactoryInterface.php b/src/SubscribePro/Service/ServiceFactoryInterface.php new file mode 100644 index 0000000..a1a584c --- /dev/null +++ b/src/SubscribePro/Service/ServiceFactoryInterface.php @@ -0,0 +1,11 @@ +config = $config; + $this->httpClient = $http; + } + + /** + * @param string $name + * @return \SubscribePro\Service\ServiceFactoryInterface|\SubscribePro\Service\AbstractServiceFactory + * @throws \SubscribePro\Exception\InvalidArgumentException + */ + public function getServiceFactory($name) + { + if (!isset($this->factories[$name])) { + $this->factories[$name] = $this->createServiceFactory($name); + } + return $this->factories[$name]; + } + + /** + * @param string $name + * @return \SubscribePro\Service\ServiceFactoryInterface + * @throws \SubscribePro\Exception\InvalidArgumentException + */ + protected function createServiceFactory($name) + { + $className = $this->getClassName($name); + + if (!class_exists($className)) { + throw new InvalidArgumentException("Service factory with '{$name}' name does not exist."); + } + + return new $className($this, $this->httpClient, $this->getServiceConfig($name)); + } + + /** + * @param string $name + * @return string + */ + private function getClassName($name) + { + $name = $this->camelize($name); + + return "SubscribePro\\Service\\{$name}\\{$name}ServiceFactory"; + } + + /** + * @param string $name + * @return array + */ + protected function getServiceConfig($name) + { + return (array)(empty($this->config[$name]) ? [] : $this->config[$name]); + } +} diff --git a/src/SubscribePro/Service/Subscription/SubscriptionService.php b/src/SubscribePro/Service/Subscription/SubscriptionService.php index 505b3f5..a0d7563 100644 --- a/src/SubscribePro/Service/Subscription/SubscriptionService.php +++ b/src/SubscribePro/Service/Subscription/SubscriptionService.php @@ -2,10 +2,16 @@ namespace SubscribePro\Service\Subscription; -use SubscribePro\Sdk; +use SubscribePro\Exception\EntityInvalidDataException; use SubscribePro\Service\AbstractService; -use SubscribePro\Exception\InvalidArgumentException; +/** + * Config options for subscription service: + * - instance_name + * Specified class must implement \SubscribePro\Service\Subscription\SubscriptionInterface interface + * Default value is \SubscribePro\Service\Subscription\Subscription + * @see \SubscribePro\Service\Subscription\SubscriptionInterface + */ class SubscriptionService extends AbstractService { /** @@ -16,19 +22,6 @@ class SubscriptionService extends AbstractService const API_NAME_SUBSCRIPTION = 'subscription'; const API_NAME_SUBSCRIPTIONS = 'subscriptions'; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - return new SubscriptionFactory( - $sdk->getAddressService()->getDataFactory(), - $sdk->getPaymentProfileService()->getDataFactory(), - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Subscription\Subscription') - ); - } - /** * @param array $subscriptionData * @return \SubscribePro\Service\Subscription\SubscriptionInterface @@ -41,13 +34,13 @@ public function createSubscription(array $subscriptionData = []) /** * @param \SubscribePro\Service\Subscription\SubscriptionInterface $subscription * @return \SubscribePro\Service\Subscription\SubscriptionInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveSubscription(SubscriptionInterface $subscription) { if (!$subscription->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $url = $subscription->isNew() ? '/services/v2/subscription.json' : "/services/v2/subscriptions/{$subscription->getId()}.json"; diff --git a/src/SubscribePro/Service/Subscription/SubscriptionServiceFactory.php b/src/SubscribePro/Service/Subscription/SubscriptionServiceFactory.php new file mode 100644 index 0000000..88d276c --- /dev/null +++ b/src/SubscribePro/Service/Subscription/SubscriptionServiceFactory.php @@ -0,0 +1,34 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + return new SubscriptionFactory( + $this->serviceFactoryResolver->getServiceFactory(AddressService::NAME)->createDataFactory(), + $this->serviceFactoryResolver->getServiceFactory(PaymentProfileService::NAME)->createDataFactory(), + $this->getConfigValue(SubscriptionService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Subscription\Subscription') + ); + } +} diff --git a/src/SubscribePro/Service/Token/TokenService.php b/src/SubscribePro/Service/Token/TokenService.php index 7b6d654..e2e8bab 100644 --- a/src/SubscribePro/Service/Token/TokenService.php +++ b/src/SubscribePro/Service/Token/TokenService.php @@ -2,10 +2,16 @@ namespace SubscribePro\Service\Token; -use SubscribePro\Sdk; +use SubscribePro\Exception\EntityInvalidDataException; use SubscribePro\Service\AbstractService; -use SubscribePro\Exception\InvalidArgumentException; +/** + * Config options for token service: + * - instance_name + * Specified class must implement \SubscribePro\Service\Token\TokenInterface interface + * Default value is \SubscribePro\Service\Token\Token + * @see \SubscribePro\Service\Token\TokenInterface + */ class TokenService extends AbstractService { /** @@ -15,17 +21,6 @@ class TokenService extends AbstractService const API_NAME_TOKEN = 'token'; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - return new TokenFactory( - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Token\Token') - ); - } - /** * @param array $tokenData * @return \SubscribePro\Service\Token\TokenInterface @@ -49,13 +44,13 @@ public function loadToken($token) /** * @param \SubscribePro\Service\Token\TokenInterface $token * @return \SubscribePro\Service\Token\TokenInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function saveToken(TokenInterface $token) { if (!$token->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post('/services/v1/vault/token.json', [self::API_NAME_TOKEN => $token->getFormData()]); diff --git a/src/SubscribePro/Service/Token/TokenServiceFactory.php b/src/SubscribePro/Service/Token/TokenServiceFactory.php new file mode 100644 index 0000000..24759c6 --- /dev/null +++ b/src/SubscribePro/Service/Token/TokenServiceFactory.php @@ -0,0 +1,30 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + return new TokenFactory( + $this->getConfigValue(TokenService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Token\Token') + ); + } +} diff --git a/src/SubscribePro/Service/Transaction/TransactionService.php b/src/SubscribePro/Service/Transaction/TransactionService.php index 1dd74b9..b2afe56 100644 --- a/src/SubscribePro/Service/Transaction/TransactionService.php +++ b/src/SubscribePro/Service/Transaction/TransactionService.php @@ -2,11 +2,17 @@ namespace SubscribePro\Service\Transaction; -use SubscribePro\Sdk; +use SubscribePro\Exception\EntityInvalidDataException; use SubscribePro\Service\AbstractService; use SubscribePro\Service\Address\AddressInterface; -use SubscribePro\Exception\InvalidArgumentException; +/** + * Config options for transaction service: + * - instance_name + * Specified class must implement \SubscribePro\Service\Transaction\TransactionInterface interface + * Default value is \SubscribePro\Service\Transaction\Transaction + * @see \SubscribePro\Service\Transaction\TransactionInterface + */ class TransactionService extends AbstractService { /** @@ -16,17 +22,6 @@ class TransactionService extends AbstractService const API_NAME_TRANSACTION = 'transaction'; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - return new TransactionFactory( - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Transaction\Transaction') - ); - } - /** * @param array $transactionData * @return \SubscribePro\Service\Transaction\TransactionInterface @@ -51,13 +46,13 @@ public function loadTransaction($transactionId) * @param int $paymentProfileId * @param \SubscribePro\Service\Transaction\TransactionInterface $transaction * @return \SubscribePro\Service\Transaction\TransactionInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function verifyProfile($paymentProfileId, TransactionInterface $transaction) { if (!$transaction->isVerifyDataValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post( @@ -71,13 +66,13 @@ public function verifyProfile($paymentProfileId, TransactionInterface $transacti * @param int $paymentProfileId * @param \SubscribePro\Service\Transaction\TransactionInterface $transaction * @return \SubscribePro\Service\Transaction\TransactionInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function authorizeByProfile($paymentProfileId, TransactionInterface $transaction) { if (!$transaction->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post( @@ -91,13 +86,13 @@ public function authorizeByProfile($paymentProfileId, TransactionInterface $tran * @param int $paymentProfileId * @param \SubscribePro\Service\Transaction\TransactionInterface $transaction * @return \SubscribePro\Service\Transaction\TransactionInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function purchaseByProfile($paymentProfileId, TransactionInterface $transaction) { if (!$transaction->isValid()) { - throw new InvalidArgumentException('Not all required fields are set.'); + throw new EntityInvalidDataException('Not all required fields are set.'); } $response = $this->httpClient->post( @@ -112,17 +107,17 @@ public function purchaseByProfile($paymentProfileId, TransactionInterface $trans * @param \SubscribePro\Service\Transaction\TransactionInterface $transaction * @param \SubscribePro\Service\Address\AddressInterface|null $address * @return \SubscribePro\Service\Token\TokenInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function authorizeByToken($token, TransactionInterface $transaction, AddressInterface $address = null) { if (!$transaction->isTokenDataValid()) { - throw new InvalidArgumentException('Not all required Transaction fields are set.'); + throw new EntityInvalidDataException('Not all required Transaction fields are set.'); } if ($address && !$address->isAsChildValid(true)) { - throw new InvalidArgumentException('Not all required Address fields are set.'); + throw new EntityInvalidDataException('Not all required Address fields are set.'); } $response = $this->httpClient->post( @@ -137,17 +132,17 @@ public function authorizeByToken($token, TransactionInterface $transaction, Addr * @param \SubscribePro\Service\Transaction\TransactionInterface $transaction * @param \SubscribePro\Service\Address\AddressInterface|null $address * @return \SubscribePro\Service\Token\TokenInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function purchaseByToken($token, TransactionInterface $transaction, AddressInterface $address = null) { if (!$transaction->isTokenDataValid()) { - throw new InvalidArgumentException('Not all required Transaction fields are set.'); + throw new EntityInvalidDataException('Not all required Transaction fields are set.'); } if ($address && !$address->isAsChildValid(true)) { - throw new InvalidArgumentException('Not all required Address fields are set.'); + throw new EntityInvalidDataException('Not all required Address fields are set.'); } $response = $this->httpClient->post( @@ -161,13 +156,13 @@ public function purchaseByToken($token, TransactionInterface $transaction, Addre * @param int $transactionId * @param \SubscribePro\Service\Transaction\TransactionInterface|null $transaction * @return \SubscribePro\Service\Transaction\TransactionInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function capture($transactionId, TransactionInterface $transaction = null) { if ($transaction && !$transaction->isServiceDataValid()) { - throw new InvalidArgumentException('Currency code not specified for given amount.'); + throw new EntityInvalidDataException('Currency code not specified for given amount.'); } $postData = $transaction ? [self::API_NAME_TRANSACTION => $transaction->getServiceFormData()] : []; @@ -179,13 +174,13 @@ public function capture($transactionId, TransactionInterface $transaction = null * @param int $transactionId * @param \SubscribePro\Service\Transaction\TransactionInterface|null $transaction * @return \SubscribePro\Service\Transaction\TransactionInterface - * @throws \SubscribePro\Exception\InvalidArgumentException + * @throws \SubscribePro\Exception\EntityInvalidDataException * @throws \SubscribePro\Exception\HttpException */ public function credit($transactionId, TransactionInterface $transaction = null) { if ($transaction && !$transaction->isServiceDataValid()) { - throw new InvalidArgumentException('Currency code not specified for given amount.'); + throw new EntityInvalidDataException('Currency code not specified for given amount.'); } $postData = $transaction ? [self::API_NAME_TRANSACTION => $transaction->getServiceFormData()] : []; diff --git a/src/SubscribePro/Service/Transaction/TransactionServiceFactory.php b/src/SubscribePro/Service/Transaction/TransactionServiceFactory.php new file mode 100644 index 0000000..c287610 --- /dev/null +++ b/src/SubscribePro/Service/Transaction/TransactionServiceFactory.php @@ -0,0 +1,30 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + return new TransactionFactory( + $this->getConfigValue(TransactionService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Transaction\Transaction') + ); + } +} diff --git a/src/SubscribePro/Service/Webhook/WebhookService.php b/src/SubscribePro/Service/Webhook/WebhookService.php index 95e9f60..615835e 100644 --- a/src/SubscribePro/Service/Webhook/WebhookService.php +++ b/src/SubscribePro/Service/Webhook/WebhookService.php @@ -2,11 +2,24 @@ namespace SubscribePro\Service\Webhook; -use SubscribePro\Sdk; use SubscribePro\Service\AbstractService; use SubscribePro\Exception\HttpException; -use SubscribePro\Service\Webhook\Event\DestinationFactory; +/** + * Config options for webhook service: + * - instance_name + * Specified class must implement \SubscribePro\Service\Webhook\EventInterface interface + * Default value is \SubscribePro\Service\Webhook\Event + * @see \SubscribePro\Service\Webhook\EventInterface + * - instance_name_destination + * Specified class must implement \SubscribePro\Service\Webhook\Event\DestinationInterface interface + * Default value is \SubscribePro\Service\Webhook\Event\Destination + * @see \SubscribePro\Service\Webhook\Event\DestinationInterface + * - instance_name_endpoint + * Specified class must implement \SubscribePro\Service\Webhook\Event\Destination\EndpointInterface interface + * Default value is \SubscribePro\Service\Webhook\Event\Destination\Endpoint + * @see \SubscribePro\Service\Webhook\Event\Destination\EndpointInterface + */ class WebhookService extends AbstractService { /** @@ -19,24 +32,6 @@ class WebhookService extends AbstractService const CONFIG_INSTANCE_NAME_DESTINATION = 'instance_name_destination'; const CONFIG_INSTANCE_NAME_ENDPOINT = 'instance_name_endpoint'; - /** - * @param \SubscribePro\Sdk $sdk - * @return \SubscribePro\Service\DataFactoryInterface - */ - protected function createDataFactory(Sdk $sdk) - { - $destinationFactory = new DestinationFactory( - $this->getConfigValue(self::CONFIG_INSTANCE_NAME_DESTINATION, '\SubscribePro\Service\Webhook\Event\Destination'), - $this->getConfigValue(self::CONFIG_INSTANCE_NAME_ENDPOINT, '\SubscribePro\Service\Webhook\Event\Destination\Endpoint') - ); - return new EventFactory( - $sdk->getCustomerService()->getDataFactory(), - $sdk->getSubscriptionService()->getDataFactory(), - $destinationFactory, - $this->getConfigValue(self::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Webhook\Event') - ); - } - /** * @return bool */ @@ -53,6 +48,7 @@ public function ping() /** * @param int $eventId * @return \SubscribePro\Service\Webhook\EventInterface + * @throws \SubscribePro\Exception\HttpException */ public function loadEvent($eventId) { diff --git a/src/SubscribePro/Service/Webhook/WebhookServiceFactory.php b/src/SubscribePro/Service/Webhook/WebhookServiceFactory.php new file mode 100644 index 0000000..50385ed --- /dev/null +++ b/src/SubscribePro/Service/Webhook/WebhookServiceFactory.php @@ -0,0 +1,41 @@ +httpClient, + $this->createDataFactory(), + $this->config + ); + } + + /** + * @return \SubscribePro\Service\DataFactoryInterface + */ + protected function createDataFactory() + { + $destinationFactory = new DestinationFactory( + $this->getConfigValue(WebhookService::CONFIG_INSTANCE_NAME_DESTINATION, '\SubscribePro\Service\Webhook\Event\Destination'), + $this->getConfigValue(WebhookService::CONFIG_INSTANCE_NAME_ENDPOINT, '\SubscribePro\Service\Webhook\Event\Destination\Endpoint') + ); + + return new EventFactory( + $this->serviceFactoryResolver->getServiceFactory(CustomerService::NAME)->createDataFactory(), + $this->serviceFactoryResolver->getServiceFactory(SubscriptionService::NAME)->createDataFactory(), + $destinationFactory, + $this->getConfigValue(WebhookService::CONFIG_INSTANCE_NAME, '\SubscribePro\Service\Webhook\Event') + ); + } +} diff --git a/src/SubscribePro/Tools/AbstractTool.php b/src/SubscribePro/Tools/AbstractTool.php new file mode 100644 index 0000000..34b46bd --- /dev/null +++ b/src/SubscribePro/Tools/AbstractTool.php @@ -0,0 +1,19 @@ +httpClient = $http; + } +} diff --git a/src/SubscribePro/Tools/Config.php b/src/SubscribePro/Tools/Config.php index 01f984f..45acd99 100644 --- a/src/SubscribePro/Tools/Config.php +++ b/src/SubscribePro/Tools/Config.php @@ -2,9 +2,7 @@ namespace SubscribePro\Tools; -use SubscribePro\Sdk; - -class Config +class Config extends AbstractTool { /** * Tool name @@ -59,19 +57,6 @@ class Config const CONFIG_CREATED = 'created'; const CONFIG_UPDATED = 'updated'; - /** - * @var \SubscribePro\Http - */ - protected $httpClient; - - /** - * @param \SubscribePro\Sdk $sdk - */ - public function __construct(Sdk $sdk) - { - $this->httpClient = $sdk->getHttp(); - } - /** * @return array * @throws \SubscribePro\Exception\HttpException diff --git a/src/SubscribePro/Tools/Report.php b/src/SubscribePro/Tools/Report.php index dc56357..ff64ef5 100644 --- a/src/SubscribePro/Tools/Report.php +++ b/src/SubscribePro/Tools/Report.php @@ -2,21 +2,15 @@ namespace SubscribePro\Tools; -use SubscribePro\Sdk; use SubscribePro\Exception\InvalidArgumentException; -class Report +class Report extends AbstractTool { /** * Tool name */ const NAME = 'report'; - /** - * @var \SubscribePro\Http - */ - protected $httpClient; - /** * Report codes */ @@ -47,14 +41,6 @@ class Report self::REPORT_PRODUCTS, ]; - /** - * @param \SubscribePro\Sdk $sdk - */ - public function __construct(Sdk $sdk) - { - $this->httpClient = $sdk->getHttp(); - } - /** * Get report in csv format * Allowed code values: @@ -81,13 +67,22 @@ public function loadReport($code, $filePath) throw new InvalidArgumentException('Invalid report code. Allowed values: ' . implode(', ', $this->reportCodes)); } - if (!is_resource($filePath) && !$this->isWritable($filePath)) { + if (!$this->isResource($filePath) && !$this->isWritable($filePath)) { throw new InvalidArgumentException("{$filePath} is not writable or a directory."); } $this->httpClient->getToSink("/services/v2/reports/{$code}", $filePath); } + /** + * @param string $filePath + * @return bool + */ + protected function isResource($filePath) + { + return is_resource($filePath); + } + /** * @param string $filePath * @return bool diff --git a/src/SubscribePro/Tools/ToolFactory.php b/src/SubscribePro/Tools/ToolFactory.php new file mode 100644 index 0000000..70f53db --- /dev/null +++ b/src/SubscribePro/Tools/ToolFactory.php @@ -0,0 +1,51 @@ +httpClient = $httpClient; + } + + /** + * @param string $name + * @return \SubscribePro\Tools\AbstractTool + * @throws \SubscribePro\Exception\InvalidArgumentException + */ + public function create($name) + { + $className = $this->getClassName($name); + + if (!class_exists($className)) { + throw new InvalidArgumentException("Tool with '{$name}' name does not exist."); + } + + return new $className($this->httpClient); + } + + /** + * @param string $name + * @return string + */ + private function getClassName($name) + { + $name = $this->camelize($name); + + return "SubscribePro\\Tools\\{$name}"; + } +} diff --git a/src/SubscribePro/Utils/StringUtils.php b/src/SubscribePro/Utils/StringUtils.php new file mode 100644 index 0000000..d7672c1 --- /dev/null +++ b/src/SubscribePro/Utils/StringUtils.php @@ -0,0 +1,24 @@ +app = new App($this->clientId, $this->clientSecret); } diff --git a/tests/HttpTest.php b/tests/HttpTest.php new file mode 100644 index 0000000..05776a6 --- /dev/null +++ b/tests/HttpTest.php @@ -0,0 +1,431 @@ +clientMock = $this->getMockBuilder('GuzzleHttp\Client') + ->disableOriginalConstructor() + ->setMethods(['get', 'post', 'put']) + ->getMock(); + $this->handlerStackMock = $this->getMockBuilder('GuzzleHttp\HandlerStack') + ->disableOriginalConstructor() + ->setMethods(['push']) + ->getMock(); + + $appMock = $this->getMockBuilder('SubscribePro\App')->disableOriginalConstructor()->getMock(); + + $this->httpMock = $this->getMockBuilder('SubscribePro\Http') + ->setMethods(['createClient', 'getHandlerStack', 'createMiddlewareLogCallback']) + ->setConstructorArgs([$appMock]) + ->getMock(); + $this->httpMock->expects($this->any())->method('createClient')->willReturn($this->clientMock); + $this->httpMock->expects($this->any())->method('getHandlerStack')->willReturn($this->handlerStackMock); + } + + /** + * @param string|null $fileName + * @param string|null $lineFormat + * @param string|null $messageFormat + * @param string $logLevel + * @param \Psr\Log\LoggerInterface $logger + * @param \GuzzleHttp\MessageFormatter $messageFormatter + * @param callable $middlewareCallback + * @dataProvider initDefaultLoggerDataProvider + */ + public function testInitDefaultLogger($fileName, $lineFormat, $messageFormat, $logLevel, $logger, $messageFormatter, $middlewareCallback) + { + $this->httpMock->expects($this->once()) + ->method('createMiddlewareLogCallback') + ->with($logger, $messageFormatter, $logLevel) + ->willReturn($middlewareCallback); + + $this->handlerStackMock->expects($this->once()) + ->method('push') + ->with($middlewareCallback, 'logger'); + + $this->httpMock->initDefaultLogger($fileName, $lineFormat, $messageFormat, $logLevel); + } + + /** + * @return array + */ + public function initDefaultLoggerDataProvider() + { + $logHandler1 = new RotatingFileHandler(Http::DEFAULT_LOG_FILE_NAME); + $logHandler1->setFormatter(new LineFormatter(Http::DEFAULT_LOG_LINE_FORMAT, null, true)); + + $logHandler2 = new RotatingFileHandler('fileName'); + $logHandler2->setFormatter(new LineFormatter('%message%', null, true)); + + return [ + 'Default params' => [ + 'fileName' => null, + 'lineFormat' => null, + 'messageFormat' => null, + 'logLevel' => LogLevel::INFO, + 'logger' => new Logger('Logger', [$logHandler1]), + 'messageFormatter' => new MessageFormatter(Http::DEFAULT_LOG_MESSAGE_FORMAT), + 'middlewareCallback' => function () {}, + ], + 'Custom params' => [ + 'fileName' => 'fileName', + 'lineFormat' => '%message%', + 'messageFormat' => '{code}', + 'logLevel' => LogLevel::NOTICE, + 'logger' => new Logger('Logger', [$logHandler2]), + 'messageFormatter' => new MessageFormatter('{code}'), + 'middlewareCallback' => function () {}, + ], + ]; + } + + public function testAddLogger() + { + $logger = new Logger('Logger'); + $messageFormatter = new MessageFormatter(); + $logLevel = LogLevel::ALERT; + $middlewareCallback = function() {}; + + $this->httpMock->expects($this->once()) + ->method('createMiddlewareLogCallback') + ->with($logger, $messageFormatter, $logLevel) + ->willReturn($middlewareCallback); + + $this->handlerStackMock->expects($this->once()) + ->method('push') + ->with($middlewareCallback, 'logger'); + + $this->httpMock->addLogger($logger, $messageFormatter, $logLevel); + } + + /** + * @param array $params + * @param array $expectedRequestParams + * @param string $url + * @param string $bodyText + * @param array $body + * @param \GuzzleHttp\Psr7\Response $response + * @dataProvider failToGetIfStatusCodeIsNotSuccessDataProvider + * @expectedException \SubscribePro\Exception\HttpException + */ + public function testFailToGetIfStatusCodeIsNotSuccess($params, $expectedRequestParams, $url, $bodyText, $body, $response) + { + $this->clientMock->expects($this->once()) + ->method('get') + ->with($url, $expectedRequestParams) + ->willReturn($response); + + $this->expectExceptionMessage($bodyText); + + $this->httpMock->get($url, $params); + } + + /** + * @return array + */ + public function failToGetIfStatusCodeIsNotSuccessDataProvider() + { + return [ + 'Without params' => [ + 'params' => [], + 'expectedRequestParams' => [], + 'url' => 'site/url', + 'bodyText' => 'error', + 'body' => ['message' => 'error'], + 'response' => new Response(300, [], json_encode(['message' => 'error'])), + ], + 'With params' => [ + 'params' => ['name' => 'John'], + 'expectedRequestParams' => [RequestOptions::QUERY => ['name' => 'John']], + 'url' => 'site/url', + 'bodyText' => 'error', + 'body' => ['message' => 'error'], + 'response' => new Response(400, [], json_encode(['message' => 'error'])), + ], + ]; + } + + /** + * @param string $url + * @param \GuzzleHttp\Psr7\Response $response + * @param array $params + * @param array $expectedRequestParams + * @param array|int $result + * @dataProvider getDataProvider + */ + public function testGet($url, $response, $params, $expectedRequestParams, $result) + { + $this->clientMock->expects($this->once()) + ->method('get') + ->with($url, $expectedRequestParams) + ->willReturn($response); + + $this->assertEquals($result, $this->httpMock->get($url, $params)); + } + + /** + * @return array + */ + public function getDataProvider() + { + return [ + 'Not empty response' => [ + 'url' => 'site/url', + 'response' => new Response(200, [], json_encode(['message' => 'success'])), + 'params' => ['name' => 'John'], + 'expectedRequestParams' => [RequestOptions::QUERY => ['name' => 'John']], + 'result' => ['message' => 'success'], + ], + 'Empty response' => [ + 'url' => 'site/url', + 'response' => new Response(201, [], ''), + 'params' => [], + 'expectedRequestParams' => [], + 'result' => 201, + ], + ]; + } + + /** + * @param array $params + * @param array $expectedRequestParams + * @param string $url + * @param string $bodyText + * @param array $body + * @param \GuzzleHttp\Psr7\Response $response + * @dataProvider failToPostIfStatusCodeIsNotSuccessDataProvider + * @expectedException \SubscribePro\Exception\HttpException + */ + public function testFailToPostIfStatusCodeIsNotSuccess($params, $expectedRequestParams, $url, $bodyText, $body, $response) + { + $this->clientMock->expects($this->once()) + ->method('post') + ->with($url, $expectedRequestParams) + ->willReturn($response); + + $this->expectExceptionMessage($bodyText); + + $this->httpMock->post($url, $params); + } + + /** + * @return array + */ + public function failToPostIfStatusCodeIsNotSuccessDataProvider() + { + return $this->getErrorPostData(); + } + + /** + * @param string $url + * @param \GuzzleHttp\Psr7\Response $response + * @param array $params + * @param array $expectedRequestParams + * @param array|int $result + * @dataProvider postDataProvider + */ + public function testPost($url, $response, $params, $expectedRequestParams, $result) + { + $this->clientMock->expects($this->once()) + ->method('post') + ->with($url, $expectedRequestParams) + ->willReturn($response); + + $this->assertEquals($result, $this->httpMock->post($url, $params)); + } + + /** + * @return array + */ + public function postDataProvider() + { + return $this->getSuccessPostData(); + } + + /** + * @param array $params + * @param array $expectedRequestParams + * @param string $url + * @param string $bodyText + * @param array $body + * @param \GuzzleHttp\Psr7\Response $response + * @dataProvider failToPutIfStatusCodeIsNotSuccessDataProvider + * @expectedException \SubscribePro\Exception\HttpException + */ + public function testFailToPutIfStatusCodeIsNotSuccess($params, $expectedRequestParams, $url, $bodyText, $body, $response) + { + $this->clientMock->expects($this->once()) + ->method('put') + ->with($url, $expectedRequestParams) + ->willReturn($response); + + $this->expectExceptionMessage($bodyText); + + $this->httpMock->put($url, $params); + } + + /** + * @return array + */ + public function failToPutIfStatusCodeIsNotSuccessDataProvider() + { + return $this->getErrorPostData(); + } + + /** + * @param string $url + * @param \GuzzleHttp\Psr7\Response $response + * @param array $params + * @param array $expectedRequestParams + * @param array|int $result + * @dataProvider putDataProvider + */ + public function testPut($url, $response, $params, $expectedRequestParams, $result) + { + $this->clientMock->expects($this->once()) + ->method('put') + ->with($url, $expectedRequestParams) + ->willReturn($response); + + $this->assertEquals($result, $this->httpMock->put($url, $params)); + } + + /** + * @return array + */ + public function putDataProvider() + { + return $this->getSuccessPostData(); + } + + /** + * @expectedException \SubscribePro\Exception\HttpException + * @expectedExceptionMessage error + */ + public function testFailToGetToSinkIfStatusCodeIsNotSuccess() + { + $url = 'site/url'; + $filePath = 'file/path'; + $response = new Response(300, [], json_encode(['message' => 'error'])); + + $this->clientMock->expects($this->once()) + ->method('get') + ->with($url, [RequestOptions::SINK => $filePath]) + ->willReturn($response); + + $this->httpMock->getToSink($url, $filePath); + } + + /** + * @param string $url + * @param \GuzzleHttp\Psr7\Response $response + * @param string $filePath + * @param array|int $result + * @dataProvider getToSinkDataProvider + */ + public function testGetToSink($url, $response, $filePath, $result) + { + $this->clientMock->expects($this->once()) + ->method('get') + ->with($url, [RequestOptions::SINK => $filePath]) + ->willReturn($response); + + $this->assertEquals($result, $this->httpMock->getToSink($url, $filePath)); + } + + /** + * @return array + */ + public function getToSinkDataProvider() + { + return [ + 'Not empty response' => [ + 'url' => 'site/url', + 'response' => new Response(200, [], json_encode(['message' => 'success'])), + 'filePath' => 'file/path', + 'result' => ['message' => 'success'], + ], + 'Empty response' => [ + 'url' => 'site/url', + 'response' => new Response(201, [], ''), + 'filePath' => 'file/path', + 'result' => 201, + ], + ]; + } + + /** + * @return array + */ + private function getErrorPostData() + { + return [ + 'Empty params' => [ + 'params' => [], + 'expectedRequestParams' => [], + 'url' => 'site/url', + 'bodyText' => 'error', + 'body' => ['message' => 'error'], + 'response' => new Response(300, [], json_encode(['message' => 'error'])), + ], + 'Not empty params' => [ + 'params' => ['name' => 'John'], + 'expectedRequestParams' => [RequestOptions::JSON => ['name' => 'John']], + 'url' => 'site/url', + 'bodyText' => 'error', + 'body' => ['message' => 'error'], + 'response' => new Response(400, [], json_encode(['message' => 'error'])), + ], + ]; + } + + /** + * @return array + */ + private function getSuccessPostData() + { + return [ + 'Not empty response' => [ + 'url' => 'site/url', + 'response' => new Response(200, [], json_encode(['message' => 'success'])), + 'params' => ['name' => 'John'], + 'expectedRequestParams' => [RequestOptions::JSON => ['name' => 'John']], + 'result' => ['message' => 'success'], + ], + 'Empty response' => [ + 'url' => 'site/url', + 'response' => new Response(201, [], ''), + 'params' => [], + 'expectedRequestParams' => [], + 'result' => 201, + ], + ]; + } +} diff --git a/tests/Service/Address/AddressServiceTest.php b/tests/Service/Address/AddressServiceTest.php new file mode 100644 index 0000000..f50cfee --- /dev/null +++ b/tests/Service/Address/AddressServiceTest.php @@ -0,0 +1,231 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->addressFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->addressService = new AddressService($this->httpClientMock, $this->addressFactoryMock); + } + + public function testCreateAddress() + { + $addressData = [ + AddressInterface::CITY => 'city', + AddressInterface::ID => 555, + ]; + $addressMock = $this->createAddressMock(); + + $this->addressFactoryMock->expects($this->once()) + ->method('create') + ->with($addressData) + ->willReturn($addressMock); + + $this->assertSame($addressMock, $this->addressService->createAddress($addressData)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveAddressIfNotValid() + { + $addressMock = $this->createAddressMock(); + $addressMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->addressService->saveAddress($addressMock); + } + + /** + * @param string $url + * @param string $itemId + * @param bool $isNew + * @param array $formData + * @param array $resultData + * @dataProvider saveAddressDataProvider + */ + public function testSaveAddress($url, $itemId, $isNew, $formData, $resultData) + { + $addressMock = $this->createAddressMock(); + $addressMock->expects($this->once())->method('isValid')->willReturn(true); + $addressMock->expects($this->once())->method('isNew')->willReturn($isNew); + $addressMock->expects($this->once())->method('getFormData')->willReturn($formData); + $addressMock->expects($this->any())->method('getId')->willReturn($itemId); + $addressMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [AddressService::API_NAME_ADDRESS => $formData]) + ->willReturn([AddressService::API_NAME_ADDRESS => $resultData]); + + $this->assertSame($addressMock, $this->addressService->saveAddress($addressMock)); + } + + /** + * @return array + */ + public function saveAddressDataProvider() + { + return [ + 'Save new address' => [ + 'url' => '/services/v2/address.json', + 'itemId' => null, + 'isNew' => true, + 'formData' => [AddressInterface::CITY => 'city'], + 'resultData' => [AddressInterface::ID => 12], + ], + 'Update existing address' => [ + 'url' => "/services/v2/addresses/11.json", + 'itemId' => 11, + 'isNew' => false, + 'formData' => [AddressInterface::CITY => 'city'], + 'resultData' => [AddressInterface::ID => 11], + ], + ]; + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToFindOrSaveAddressIfNotValid() + { + $addressMock = $this->createAddressMock(); + $addressMock->expects($this->once())->method('isValid')->willReturn(false); + + $this->httpClientMock->expects($this->never()) + ->method('post'); + + $this->addressService->findOrSave($addressMock); + } + + public function testFindOrSaveAddress() + { + $url = '/services/v2/address/find-or-create.json'; + $formData = [AddressInterface::CITY => 'city']; + $expectedImportData = [AddressInterface::ID => '111']; + + $addressMock = $this->createAddressMock(); + $addressMock->expects($this->once())->method('isValid')->willReturn(true); + $addressMock->expects($this->once())->method('getFormData')->willReturn($formData); + $addressMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [AddressService::API_NAME_ADDRESS => $formData]) + ->willReturn([AddressService::API_NAME_ADDRESS => $expectedImportData]); + + $this->assertSame($addressMock, $this->addressService->findOrSave($addressMock)); + } + + public function testLoadAddress() + { + $itemId = 111; + $itemData = [ + AddressInterface::ID => $itemId, + AddressInterface::CITY => 'city', + ]; + $addressMock = $this->createAddressMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v2/addresses/{$itemId}.json") + ->willReturn([AddressService::API_NAME_ADDRESS => $itemData]); + + $this->addressFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($addressMock); + + $this->assertSame($addressMock, $this->addressService->loadAddress($itemId)); + } + + /** + * @param int $customerId + * @param array $filters + * @param array $itemsData + * @dataProvider loadAddressesDataProvider + */ + public function testLoadAddresses($customerId, $filters, $itemsData) + { + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with('/services/v2/addresses.json', $filters) + ->willReturn([AddressService::API_NAME_ADDRESSES => $itemsData]); + + $addresses = []; + $addressFactoryMap = []; + foreach ($itemsData as $itemData) { + $address = $this->createAddressMock(); + $addressFactoryMap[] = [$itemData, $address]; + $addresses[] = $address; + } + $this->addressFactoryMock->expects($this->exactly(count($itemsData))) + ->method('create') + ->willReturnMap($addressFactoryMap); + + $this->assertSame($addresses, $this->addressService->loadAddresses($customerId)); + } + + /** + * @return array + */ + public function loadAddressesDataProvider() + { + return [ + 'Loading without filter' => [ + 'customerId' => null, + 'filters' => [], + 'itemsData' => [[AddressInterface::ID => 111], [AddressInterface::ID => 222]] + ], + 'Loading by customer ID' => [ + 'customerId' => 123, + 'filters' => [AddressInterface::CUSTOMER_ID => 123], + 'itemsData' => [[AddressInterface::CUSTOMER_ID => 123, AddressInterface::ID => 111]] + ] + ]; + } + + /** + * @return \SubscribePro\Service\Address\AddressInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createAddressMock() + { + return $this->getMockBuilder('SubscribePro\Service\Address\AddressInterface')->getMock(); + } +} diff --git a/tests/Service/Address/AddressTest.php b/tests/Service/Address/AddressTest.php new file mode 100644 index 0000000..93259c1 --- /dev/null +++ b/tests/Service/Address/AddressTest.php @@ -0,0 +1,273 @@ +address = new Address(); + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isValidDataProvider + */ + public function testIsValid($data, $isValid) + { + $this->address->importData($data); + $this->assertEquals($isValid, $this->address->isValid()); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return [ + 'Not valid: new: without customer ID' => [ + 'data' => [ + AddressInterface::LAST_NAME => 'surname', + AddressInterface::FIRST_NAME => 'name', + ], + 'isValid' => false + ], + 'Not valid: new: without first name' => [ + 'data' => [ + AddressInterface::LAST_NAME => 'surname', + AddressInterface::CUSTOMER_ID => 111, + ], + 'isValid' => false + ], + 'Not valid: new: without last name' => [ + 'data' => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::CUSTOMER_ID => 111, + ], + 'isValid' => false + ], + 'Valid: new' => [ + 'data' => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + AddressInterface::CUSTOMER_ID => 111, + ], + 'isValid' => true + ], + 'Valid: not new' => [ + 'data' => [ + AddressInterface::ID => 123, + ], + 'isValid' => true + ] + ]; + } + + /** + * @param array $data + * @param bool $isNew + * @param bool $isValid + * @dataProvider isAsChildValidDataProvider + */ + public function testIsAsChildValid($data, $isNew, $isValid) + { + $this->address->importData($data); + $this->assertEquals($isValid, $this->address->isAsChildValid($isNew)); + } + + /** + * @return array + */ + public function isAsChildValidDataProvider() + { + return [ + 'Not valid: new: without last name' => [ + 'data' => [ + AddressInterface::FIRST_NAME => 'name', + ], + 'isNew' => true, + 'isValid' => false, + ], + 'Not valid: new: without first name' => [ + 'data' => [ + AddressInterface::LAST_NAME => 'surname', + ], + 'isNew' => true, + 'isValid' => false, + ], + 'Valid: new' => [ + 'data' => [ + AddressInterface::LAST_NAME => 'surname', + AddressInterface::FIRST_NAME => 'name', + ], + 'isNew' => true, + 'isValid' => true, + ], + 'Valid: not new' => [ + 'data' => [], + 'isNew' => false, + 'isValid' => true, + ] + ]; + } + + /** + * @param array $data + * @param array $expectedData + * @dataProvider getFormDataProvider + */ + public function testGetFormData($data, $expectedData) + { + $this->address->importData($data); + $this->assertEquals($expectedData, $this->address->getFormData()); + } + + /** + * @return array + */ + public function getFormDataProvider() + { + return [ + 'New address' => [ + 'data' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + 'expectedData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + ], + 'Not new address' => [ + 'data' => [ + AddressInterface::ID => 111, + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::CUSTOMER_ID => '111', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + 'expectedData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + ], + ]; + } + + /** + * @param array $data + * @dataProvider getAsChildFormDataProvider + */ + public function testGetAsChildFormData($data) + { + $expectedData = [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ]; + + $this->address->importData($data); + $this->assertEquals($expectedData, $this->address->getFormData()); + } + + /** + * @return array + */ + public function getAsChildFormDataProvider() + { + return [ + 'New address' => [ + 'data' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + ], + 'Not new address' => [ + 'data' => [ + AddressInterface::ID => 111, + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::CUSTOMER_ID => '111', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + ], + ]; + } +} diff --git a/tests/Service/Customer/CustomerServiceTest.php b/tests/Service/Customer/CustomerServiceTest.php new file mode 100644 index 0000000..52b2da5 --- /dev/null +++ b/tests/Service/Customer/CustomerServiceTest.php @@ -0,0 +1,206 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->customerFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->customerService = new CustomerService($this->httpClientMock, $this->customerFactoryMock); + } + + public function testCreateCustomer() + { + $customerMock = $this->createCustomerMock(); + $customerData = [ + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::ID => 123, + ]; + + $this->customerFactoryMock->expects($this->once()) + ->method('create') + ->with($customerData) + ->willReturn($customerMock); + + $this->assertSame($customerMock, $this->customerService->createCustomer($customerData)); + } + + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveCustomerIfNotValid() + { + $customerMock = $this->createCustomerMock(); + $customerMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->customerService->saveCustomer($customerMock); + } + + /** + * @param string $url + * @param string $itemId + * @param bool $isNew + * @param array $formData + * @param array $resultData + * @dataProvider saveCustomerDataProvider + */ + public function testSaveCustomer($url, $itemId, $isNew, $formData, $resultData) + { + $customerMock = $this->createCustomerMock(); + $customerMock->expects($this->once())->method('isValid')->willReturn(true); + $customerMock->expects($this->once())->method('isNew')->willReturn($isNew); + $customerMock->expects($this->once())->method('getFormData')->willReturn($formData); + $customerMock->expects($this->any())->method('getId')->willReturn($itemId); + $customerMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [CustomerService::API_NAME_CUSTOMER => $formData]) + ->willReturn([CustomerService::API_NAME_CUSTOMER => $resultData]); + + $this->assertSame($customerMock, $this->customerService->saveCustomer($customerMock)); + } + + /** + * @return array + */ + public function saveCustomerDataProvider() + { + return [ + 'Save new customer' => [ + 'url' => '/services/v2/customer.json', + 'itemId' => null, + 'isNew' => true, + 'formData' => [CustomerInterface::EMAIL => 'email@example.com'], + 'resultData' => [CustomerInterface::ID => 11], + ], + 'Update existing customer' => [ + 'url' => '/services/v2/customers/22.json', + 'itemId' => 22, + 'isNew' => false, + 'formData' => [CustomerInterface::EMAIL => 'email@example.com'], + 'resultData' => [CustomerInterface::ID => 22], + ], + ]; + } + + public function testLoadCustomer() + { + $itemId = 111; + $itemData = [ + CustomerInterface::ID => $itemId, + CustomerInterface::EMAIL => 'email@example.com', + ]; + $customerMock = $this->createCustomerMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v2/customers/{$itemId}.json") + ->willReturn([CustomerService::API_NAME_CUSTOMER => $itemData]); + + $this->customerFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($customerMock); + + $this->assertSame($customerMock, $this->customerService->loadCustomer($itemId)); + } + + /** + * @expectedException \SubscribePro\Exception\InvalidArgumentException + * @expectedExceptionMessageRegExp /Only \[[a-z,_ ]+\] query filters are allowed./ + */ + public function testFailToLoadCustomersIfFilterIsNotValid() + { + $filters = ['invalid' => true]; + + $this->httpClientMock->expects($this->never()) + ->method('get'); + + $this->customerService->loadCustomers($filters); + } + + /** + * @param array $filters + * @param array $itemsData + * @dataProvider loadCustomersDataProvider + */ + public function testLoadCustomers($filters, $itemsData) + { + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with('/services/v2/customers.json', $filters) + ->willReturn([CustomerService::API_NAME_CUSTOMERS => $itemsData]); + + $customers = []; + $customerFactoryMap = []; + foreach ($itemsData as $itemData) { + $customer = $this->createCustomerMock(); + $customerFactoryMap[] = [$itemData, $customer]; + $customers[] = $customer; + } + $this->customerFactoryMock->expects($this->exactly(count($itemsData))) + ->method('create') + ->willReturnMap($customerFactoryMap); + + $this->assertSame($customers, $this->customerService->loadCustomers($filters)); + } + + /** + * @return array + */ + public function loadCustomersDataProvider() + { + return [ + 'Loading without filter' => [ + 'filters' => [], + 'itemsData' => [[CustomerInterface::ID => 111], [CustomerInterface::ID => 222]], + ], + 'Loading by first name' => [ + 'filters' => [CustomerInterface::FIRST_NAME => 'John'], + 'itemsData' => [[CustomerInterface::ID => 333, CustomerInterface::FIRST_NAME => 'John']], + ], + ]; + } + + /** + * @return \SubscribePro\Service\Customer\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createCustomerMock() + { + return $this->getMockBuilder('SubscribePro\Service\Customer\CustomerInterface')->getMock(); + } +} diff --git a/tests/Service/Customer/CustomerTest.php b/tests/Service/Customer/CustomerTest.php new file mode 100644 index 0000000..55b5a91 --- /dev/null +++ b/tests/Service/Customer/CustomerTest.php @@ -0,0 +1,151 @@ +customer = new Customer(); + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isValidDataProvider + */ + public function testIsValid($data, $isValid) + { + $this->customer->importData($data); + $this->assertEquals($isValid, $this->customer->isValid()); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return [ + 'Not valid: new: without email' => [ + 'data' => [ + CustomerInterface::FIRST_NAME => 'name', + CustomerInterface::LAST_NAME => 'surname', + ], + 'isValid' => false + ], + 'Not valid: new: without first name' => [ + 'data' => [ + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::LAST_NAME => 'surname', + ], + 'isValid' => false + ], + 'Not valid: new: without last name' => [ + 'data' => [ + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::FIRST_NAME => 'name', + ], + 'isValid' => false + ], + 'Valid: new' => [ + 'data' => [ + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::FIRST_NAME => 'name', + CustomerInterface::LAST_NAME => 'surname', + ], + 'isValid' => true + ], + 'Valid: not new' => [ + 'data' => [ + CustomerInterface::ID => 123, + ], + 'isValid' => true + ], + ]; + } + + /** + * @param array $data + * @param array $expectedData + * @dataProvider getFormDataProvider + */ + public function testGetFormData($data, $expectedData) + { + $this->customer->importData($data); + $this->assertEquals($expectedData, $this->customer->getFormData()); + } + + /** + * @return array + */ + public function getFormDataProvider() + { + return [ + 'New address' => [ + 'data' => [ + CustomerInterface::ACTIVE_SUBSCRIBED_QTY => '123', + CustomerInterface::ACTIVE_SUBSCRIPTION_COUNT => '222', + CustomerInterface::CREATE_MAGENTO_CUSTOMER => false, + CustomerInterface::FIRST_NAME => 'first name', + CustomerInterface::LAST_NAME => 'last name', + CustomerInterface::MIDDLE_NAME => 'middle name', + CustomerInterface::MAGENTO_CUSTOMER_ID => 123, + CustomerInterface::MAGENTO_CUSTOMER_GROUP_ID => 321, + CustomerInterface::MAGENTO_WEBSITE_ID => 333, + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::CREATED => '2016-12-12', + CustomerInterface::UPDATED => '2016-12-12', + CustomerInterface::EXTERNAL_VAULT_CUSTOMER_TOKEN => 'token', + CustomerInterface::SUBSCRIPTION_COUNT => '123', + ], + 'expectedData' => [ + CustomerInterface::FIRST_NAME => 'first name', + CustomerInterface::LAST_NAME => 'last name', + CustomerInterface::MIDDLE_NAME => 'middle name', + CustomerInterface::CREATE_MAGENTO_CUSTOMER => false, + CustomerInterface::MAGENTO_CUSTOMER_ID => 123, + CustomerInterface::MAGENTO_CUSTOMER_GROUP_ID => 321, + CustomerInterface::MAGENTO_WEBSITE_ID => 333, + CustomerInterface::EMAIL => 'email@example.com', + ], + ], + 'Not new address' => [ + 'data' => [ + CustomerInterface::ID => 444, + CustomerInterface::ACTIVE_SUBSCRIBED_QTY => '123', + CustomerInterface::ACTIVE_SUBSCRIPTION_COUNT => '222', + CustomerInterface::CREATE_MAGENTO_CUSTOMER => false, + CustomerInterface::FIRST_NAME => 'first name', + CustomerInterface::LAST_NAME => 'last name', + CustomerInterface::MIDDLE_NAME => 'middle name', + CustomerInterface::MAGENTO_CUSTOMER_ID => 123, + CustomerInterface::MAGENTO_CUSTOMER_GROUP_ID => 321, + CustomerInterface::MAGENTO_WEBSITE_ID => 333, + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::CREATED => '2016-12-12', + CustomerInterface::UPDATED => '2016-12-12', + CustomerInterface::EXTERNAL_VAULT_CUSTOMER_TOKEN => 'token', + CustomerInterface::SUBSCRIPTION_COUNT => '123', + ], + 'expectedData' => [ + CustomerInterface::EXTERNAL_VAULT_CUSTOMER_TOKEN => 'token', + CustomerInterface::FIRST_NAME => 'first name', + CustomerInterface::LAST_NAME => 'last name', + CustomerInterface::MIDDLE_NAME => 'middle name', + CustomerInterface::MAGENTO_CUSTOMER_ID => 123, + CustomerInterface::MAGENTO_CUSTOMER_GROUP_ID => 321, + CustomerInterface::MAGENTO_WEBSITE_ID => 333, + CustomerInterface::EMAIL => 'email@example.com', + ], + ], + ]; + } +} diff --git a/tests/Service/PaymentProfile/PaymentProfileServiceTest.php b/tests/Service/PaymentProfile/PaymentProfileServiceTest.php new file mode 100644 index 0000000..2b0c730 --- /dev/null +++ b/tests/Service/PaymentProfile/PaymentProfileServiceTest.php @@ -0,0 +1,366 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentProfileFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->paymentProfileService = new PaymentProfileService($this->httpClientMock, $this->paymentProfileFactoryMock); + } + + public function testCreateProfile() + { + $paymentProfileData = [ + PaymentProfileInterface::ID => 123, + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + ]; + $profileMock = $this->createProfileMock(); + + $this->paymentProfileFactoryMock->expects($this->once()) + ->method('create') + ->with($paymentProfileData) + ->willReturn($profileMock); + + $this->assertSame($profileMock, $this->paymentProfileService->createProfile($paymentProfileData)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveProfileIfNotValid() + { + $paymentProfileMock = $this->createProfileMock(); + $paymentProfileMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + $this->httpClientMock->expects($this->never())->method('put'); + + $this->paymentProfileService->saveProfile($paymentProfileMock); + } + + /** + * @param string $url + * @param string $itemId + * @param bool $isNew + * @param string $method + * @param array $formData + * @param array $resultData + * @dataProvider saveProfileDataProvider + */ + public function testSaveProfile($url, $itemId, $isNew, $method, $formData, $resultData) + { + $profileMock = $this->createProfileMock(); + $profileMock->expects($this->once())->method('isValid')->willReturn(true); + $profileMock->expects($this->once())->method('isNew')->willReturn($isNew); + $profileMock->expects($this->once())->method('getFormData')->willReturn($formData); + $profileMock->expects($this->any())->method('getId')->willReturn($itemId); + $profileMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturn($profileMock); + + $this->httpClientMock->expects($this->once()) + ->method($method) + ->with($url, [PaymentProfileService::API_NAME_PROFILE => $formData]) + ->willReturn([PaymentProfileService::API_NAME_PROFILE => $resultData]); + + $this->assertSame($profileMock, $this->paymentProfileService->saveProfile($profileMock)); + } + + /** + * @return array + */ + public function saveProfileDataProvider() + { + return [ + 'Save new profile' => [ + 'url' => '/services/v1/vault/paymentprofile.json', + 'itemId' => null, + 'isNew' => true, + 'method' => 'post', + 'formData' => [PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '123'], + 'resultData' => [PaymentProfileInterface::ID => '111'], + ], + 'Update existing profile' => [ + 'url' => '/services/v1/vault/paymentprofiles/22.json', + 'itemId' => 22, + 'isNew' => false, + 'method' => 'put', + 'formData' => [PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '123'], + 'resultData' => [PaymentProfileInterface::ID => '22'], + ], + ]; + } + + public function testLoadProfile() + { + $itemId = 111; + $itemData = [PaymentProfileInterface::ID => $itemId]; + $paymentProfileMock = $this->createProfileMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v1/vault/paymentprofiles/{$itemId}.json") + ->willReturn([PaymentProfileService::API_NAME_PROFILE => $itemData]); + + $this->paymentProfileFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($paymentProfileMock); + + $this->assertSame($paymentProfileMock, $this->paymentProfileService->loadProfile($itemId)); + } + + public function testLoadProfileByToken() + { + $token = 'token'; + $itemData = [PaymentProfileInterface::PAYMENT_TOKEN => $token]; + $paymentProfileMock = $this->createProfileMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v1/vault/tokens/{$token}/paymentprofile.json") + ->willReturn([PaymentProfileService::API_NAME_PROFILE => $itemData]); + + $this->paymentProfileFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($paymentProfileMock); + + $this->assertSame($paymentProfileMock, $this->paymentProfileService->loadProfileByToken($token)); + } + + public function testRedactProfile() + { + $itemId = 111; + $itemData = [PaymentProfileInterface::ID => $itemId]; + $paymentProfileMock = $this->createProfileMock(); + + $this->httpClientMock->expects($this->once()) + ->method('put') + ->with("/services/v1/vault/paymentprofiles/{$itemId}/redact.json") + ->willReturn([PaymentProfileService::API_NAME_PROFILE => $itemData]); + + $this->paymentProfileFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($paymentProfileMock); + + $this->assertSame($paymentProfileMock, $this->paymentProfileService->redactProfile($itemId)); + } + + /** + * @expectedException \SubscribePro\Exception\InvalidArgumentException + * @expectedExceptionMessageRegExp /Only \[[a-z,_ ]+\] query filters are allowed./ + */ + public function testFailToLoadProfilesIfFilterIsNotValid() + { + $filters = ['invalid' => true]; + + $this->httpClientMock->expects($this->never())->method('get'); + + $this->paymentProfileService->loadProfiles($filters); + } + + /** + * @param array $filters + * @param array $itemsData + * @dataProvider loadProfilesDataProvider + */ + public function testLoadProfiles($filters, $itemsData) + { + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with('/services/v1/vault/paymentprofiles.json', $filters) + ->willReturn([PaymentProfileService::API_NAME_PROFILES => $itemsData]); + + $profiles = []; + $paymentProfileFactoryMap = []; + foreach ($itemsData as $itemData) { + $paymentProfile = $this->createProfileMock(); + $paymentProfileFactoryMap[] = [$itemData, $paymentProfile]; + $profiles[] = $paymentProfile; + } + $this->paymentProfileFactoryMock->expects($this->exactly(count($itemsData))) + ->method('create') + ->willReturnMap($paymentProfileFactoryMap); + + $this->assertSame($profiles, $this->paymentProfileService->loadProfiles($filters)); + } + + /** + * @return array + */ + public function loadProfilesDataProvider() + { + return [ + 'Loading without filter' => [ + 'filters' => [], + 'itemsData' => [ + [PaymentProfileInterface::ID => 111], + [PaymentProfileInterface::ID => 222] + ], + ], + 'Loading by magento_customer_id' => [ + 'filters' => [PaymentProfileInterface::MAGENTO_CUSTOMER_ID => 123], + 'itemsData' => [[ + PaymentProfileInterface::ID => 333, + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => 123 + ]], + ], + ]; + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveThirdPartyTokenIfProfileIsNotValid() + { + $paymentProfileMock = $this->createProfileMock(); + $paymentProfileMock->expects($this->once()) + ->method('isThirdPartyDataValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->paymentProfileService->saveThirdPartyToken($paymentProfileMock); + } + + public function testSaveThirdPartyToken() + { + $formData = [PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '123']; + $expectedImportData = [PaymentProfileInterface::ID => '111']; + + $paymentProfileMock = $this->createProfileMock(); + $paymentProfileMock->expects($this->once())->method('isThirdPartyDataValid')->willReturn(true); + $paymentProfileMock->expects($this->once())->method('getThirdPartyTokenFormData')->willReturn($formData); + $paymentProfileMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with('/services/v2/paymentprofile/third-party-token.json', [PaymentProfileService::API_NAME_PROFILE => $formData]) + ->willReturn([PaymentProfileService::API_NAME_PROFILE => $expectedImportData]); + + $this->assertSame($paymentProfileMock, $this->paymentProfileService->saveThirdPartyToken($paymentProfileMock)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveTokenIfProfileIsNotValid() + { + $token = 'token'; + $paymentProfileMock = $this->createProfileMock(); + $paymentProfileMock->expects($this->once()) + ->method('isTokenDataValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never()) + ->method('post'); + + $this->paymentProfileService->saveToken($token, $paymentProfileMock); + } + + public function testSaveToken() + { + $token = 'token'; + $formData = [PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '123']; + $expectedImportData = [PaymentProfileInterface::ID => '111']; + + $paymentProfileMock = $this->createProfileMock(); + $paymentProfileMock->expects($this->once())->method('isTokenDataValid')->willReturn(true); + $paymentProfileMock->expects($this->once())->method('getTokenFormData')->willReturn($formData); + $paymentProfileMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v1/vault/tokens/{$token}/store.json", [PaymentProfileService::API_NAME_PROFILE => $formData]) + ->willReturn([PaymentProfileService::API_NAME_PROFILE => $expectedImportData]); + + $this->assertSame($paymentProfileMock, $this->paymentProfileService->saveToken($token, $paymentProfileMock)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToVerifyAndSaveTokenIfProfileIsNotValid() + { + $token = 'token'; + + $paymentProfileMock = $this->createProfileMock(); + $paymentProfileMock->expects($this->once()) + ->method('isTokenDataValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->paymentProfileService->verifyAndSaveToken($token, $paymentProfileMock); + } + + public function testVerifyAndSaveToken() + { + $token = 'token'; + $formData = [PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '123']; + $expectedImportData = [PaymentProfileInterface::ID => '111']; + + $paymentProfileMock = $this->createProfileMock(); + $paymentProfileMock->expects($this->once())->method('isTokenDataValid')->willReturn(true); + $paymentProfileMock->expects($this->once())->method('getTokenFormData')->willReturn($formData); + $paymentProfileMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v1/vault/tokens/{$token}/verifyandstore.json", [PaymentProfileService::API_NAME_PROFILE => $formData]) + ->willReturn([PaymentProfileService::API_NAME_PROFILE => $expectedImportData]); + + $this->assertSame($paymentProfileMock, $this->paymentProfileService->verifyAndSaveToken($token, $paymentProfileMock)); + } + + /** + * @return \SubscribePro\Service\PaymentProfile\PaymentProfileInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createProfileMock() + { + return $this->getMockBuilder('SubscribePro\Service\PaymentProfile\PaymentProfileInterface')->getMock(); + } +} diff --git a/tests/Service/PaymentProfile/PaymentProfileTest.php b/tests/Service/PaymentProfile/PaymentProfileTest.php new file mode 100644 index 0000000..01df34a --- /dev/null +++ b/tests/Service/PaymentProfile/PaymentProfileTest.php @@ -0,0 +1,949 @@ +billingAddressMock = $this->getMockBuilder('SubscribePro\Service\Address\Address') + ->disableOriginalConstructor() + ->getMock(); + + $this->paymentProfile = new PaymentProfile([ + PaymentProfileInterface::BILLING_ADDRESS => $this->billingAddressMock + ]); + } + + /** + * @param array $data + * @param array $billingData + * @param array $expectedData + * @dataProvider importDataDataProvider + */ + public function testImportData($data, $billingData, $expectedData) + { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('importData') + ->with($billingData) + ->willReturnSelf(); + + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('toArray') + ->willReturn($billingData); + + $this->paymentProfile->importData($data); + $this->assertEquals($expectedData, $this->paymentProfile->toArray()); + } + + /** + * @return array + */ + public function importDataDataProvider() + { + return [ + 'Empty data' => [ + 'data' => [], + 'billingData' => [], + 'expectedData' => [ + PaymentProfileInterface::BILLING_ADDRESS => [] + ] + ], + 'Billing address data is not array' => [ + 'data' => [ + PaymentProfileInterface::BILLING_ADDRESS => 'invalid' + ], + 'billingData' => [], + 'expectedData' => [ + PaymentProfileInterface::BILLING_ADDRESS => [] + ] + ], + 'Billing address data is an array' => [ + 'data' => [ + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city' + ] + ], + 'billingData' => [AddressInterface::CITY => 'city'], + 'expectedData' => [ + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city' + ] + ] + ], + ]; + } + + public function testImportDataWithAddressInstance() + { + $data = [ + PaymentProfileInterface::ID => 111, + PaymentProfileInterface::BILLING_ADDRESS => $this->billingAddressMock + ]; + $billingData = [AddressInterface::CITY => 'city']; + $expectedData = [ + PaymentProfileInterface::ID => 111, + PaymentProfileInterface::BILLING_ADDRESS => $billingData + ]; + + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('toArray') + ->willReturn($billingData); + + $this->paymentProfile->importData($data); + $this->assertEquals($expectedData, $this->paymentProfile->toArray()); + } + + public function testToArray() + { + $billingData = ['test_key' => 'test_value']; + + $expectedData = [ + PaymentProfileInterface::ID => 111, + PaymentProfileInterface::CUSTOMER_ID => 123, + PaymentProfileInterface::BILLING_ADDRESS => $billingData + ]; + + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('toArray') + ->willReturn($billingData); + + $this->paymentProfile->setId(111); + $this->paymentProfile->setCustomerId(123); + + $this->assertEquals($expectedData, $this->paymentProfile->toArray()); + } + + /** + * @param array $data + * @param array $billingData + * @param bool $isValid + * @param bool $isAsChildValid + * @dataProvider isValidDataProvider + */ + public function testIsValid($data, $billingData, $isValid, $isAsChildValid) + { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('importData') + ->with($billingData) + ->willReturnSelf(); + + if (null === $isAsChildValid) { + $this->billingAddressMock->expects($this->never())->method('isAsChildValid'); + } else { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('isAsChildValid') + ->willReturn($isAsChildValid); + } + + $this->paymentProfile->importData($data); + $this->assertEquals($isValid, $this->paymentProfile->isValid()); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return [ + 'Not valid: new: empty data' => [ + 'data' => [], + 'billingData' => [], + 'isValid' => false, + 'isAsChildValid' => null + ], + 'Not valid: new: empty billing address' => [ + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => 11, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_YEAR => '2020', + PaymentProfileInterface::BILLING_ADDRESS => [] + ], + 'billingData' => [], + 'isValid' => false, + 'isAsChildValid' => false + ], + 'Not valid: new: without customer ID' => [ + 'data' => [ + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_YEAR => '2020', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ] + ], + 'billingData' => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ], + 'isValid' => false, + 'isAsChildValid' => null + ], + 'Not valid: new: without credit card number' => [ + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => 11, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2020', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ] + ], + 'billingData' => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ], + 'isValid' => false, + 'isAsChildValid' => null + ], + 'Valid: new: with customer ID' => [ + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => 11, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_YEAR => '2020', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ] + ], + 'billingData' => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ], + 'isValid' => true, + 'isAsChildValid' => true + ], + 'Valid: new: with magento customer ID' => [ + 'data' => [ + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => 11, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_YEAR => '2020', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ] + ], + 'billingData' => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ], + 'isValid' => true, + 'isAsChildValid' => true + ], + 'Valid: not new' => [ + 'data' => [ + PaymentProfileInterface::ID => 11, + ], + 'billingData' => [], + 'isValid' => true, + 'isAsChildValid' => true + ], + 'Not valid: billing address not valid' => [ + 'data' => [ + PaymentProfileInterface::ID => 11, + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ] + ], + 'billingData' => [ + AddressInterface::FIRST_NAME => 'name', + AddressInterface::LAST_NAME => 'surname', + ], + 'isValid' => false, + 'isAsChildValid' => false + ], + ]; + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isTokenDataValidDataProvider + */ + public function testIsTokenDataValid($data, $isValid) + { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('importData') + ->with([]) + ->willReturnSelf(); + + $this->paymentProfile->importData($data); + $this->assertEquals($isValid, $this->paymentProfile->isTokenDataValid()); + } + + /** + * @return array + */ + public function isTokenDataValidDataProvider() + { + return [ + 'Not valid: empty data' => [ + 'data' => [], + 'isValid' => false + ], + 'Valid: with customer ID' => [ + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => 11, + ], + 'isValid' => true + ], + 'Valid: with magento customer ID' => [ + 'data' => [ + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => 11, + ], + 'isValid' => true + ], + ]; + } + + /** + * @param array $data + * @param array $billingData + * @param bool $isValid + * @param bool $isAsChildValid + * @dataProvider isThirdPartyDataValidDataProvider + */ + public function testIsThirdPartyDataValid($data, $billingData, $isValid, $isAsChildValid) + { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('importData') + ->with($billingData) + ->willReturnSelf(); + + if (null === $isAsChildValid) { + $this->billingAddressMock->expects($this->never())->method('isAsChildValid'); + } else { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('isAsChildValid') + ->willReturn($isAsChildValid); + } + + $this->paymentProfile->importData($data); + $this->assertEquals($isValid, $this->paymentProfile->isThirdPartyDataValid()); + } + + /** + * @return array + */ + public function isThirdPartyDataValidDataProvider() + { + return [ + 'Not valid: without third party token' => [ + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => 11, + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city' + ], + ], + 'billingData' => [ + AddressInterface::CITY => 'city' + ], + 'isValid' => false, + 'isAsChildValid' => null + ], + 'Not valid: without customer ID' => [ + 'data' => [ + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city' + ], + ], + 'billingData' => [ + AddressInterface::CITY => 'city' + ], + 'isValid' => false, + 'isAsChildValid' => null + ], + 'Not valid: billing address is not valid' => [ + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => 11, + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city' + ], + ], + 'billingData' => [ + AddressInterface::CITY => 'city' + ], + 'isValid' => false, + 'isAsChildValid' => false + ], + 'Valid' => [ + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => 11, + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city' + ], + ], + 'billingData' => [ + AddressInterface::CITY => 'city' + ], + 'isValid' => true, + 'isAsChildValid' => true + ], + ]; + } + + /** + * @param bool $isNew + * @param array $data + * @param array $billingData + * @param array $expectedData + * @param array $billingFormData + * @dataProvider getFormDataProvider + */ + public function testGetFormData($isNew, $data, $billingData, $expectedData, $billingFormData) + { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('importData') + ->with($billingData) + ->willReturnSelf(); + + $this->billingAddressMock->expects($this->once()) + ->method('getAsChildFormData') + ->with($isNew) + ->willReturn($billingFormData); + + $this->paymentProfile->importData($data); + $this->assertEquals($expectedData, $this->paymentProfile->getFormData()); + } + + /** + * @return array + */ + public function getFormDataProvider() + { + return [ + 'New profile without address' => [ + 'isNew' => true, + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CUSTOMER_EMAIL => '125', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::GATEWAY => 'gateway', + PaymentProfileInterface::PAYMENT_METHOD_TYPE => 'card', + PaymentProfileInterface::PAYMENT_TOKEN => 'token', + PaymentProfileInterface::PAYMENT_VAULT => 'vault', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::STATUS => 'success', + PaymentProfileInterface::CREATED => '2016-12-12', + PaymentProfileInterface::UPDATED => '2016-12-12', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::MAGENTO_ADDRESS_ID => '23', + ] + ], + 'billingData' => [AddressInterface::MAGENTO_ADDRESS_ID => '23'], + 'expectedData' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + ], + 'billingFormData' => [] + ], + 'New profile with address' => [ + 'isNew' => true, + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CUSTOMER_EMAIL => '125', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::GATEWAY => 'gateway', + PaymentProfileInterface::PAYMENT_METHOD_TYPE => 'card', + PaymentProfileInterface::PAYMENT_TOKEN => 'token', + PaymentProfileInterface::PAYMENT_VAULT => 'vault', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::STATUS => 'success', + PaymentProfileInterface::CREATED => '2016-12-12', + PaymentProfileInterface::UPDATED => '2016-12-12', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + 'billingData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + 'expectedData' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country' + ] + ], + 'billingFormData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country' + ] + ], + 'Not new profile with address' => [ + 'isNew' => false, + 'data' => [ + PaymentProfileInterface::ID => 555, + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CUSTOMER_EMAIL => '125', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::GATEWAY => 'gateway', + PaymentProfileInterface::PAYMENT_METHOD_TYPE => 'card', + PaymentProfileInterface::PAYMENT_TOKEN => 'token', + PaymentProfileInterface::PAYMENT_VAULT => 'vault', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::STATUS => 'success', + PaymentProfileInterface::CREATED => '2016-12-12', + PaymentProfileInterface::UPDATED => '2016-12-12', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + 'billingData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + 'expectedData' => [ + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + 'billingFormData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2' + ] + ], + ]; + } + + /** + * @param bool $isNew + * @param array $data + * @param array $billingData + * @param array $expectedData + * @param array $billingFormData + * @dataProvider getTokenFormDataProvider + */ + public function testGetTokenFormData($isNew, $data, $billingData, $expectedData, $billingFormData) + { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('importData') + ->with($billingData) + ->willReturnSelf(); + + $this->billingAddressMock->expects($this->once()) + ->method('getAsChildFormData') + ->with($isNew) + ->willReturn($billingFormData); + + $this->paymentProfile->importData($data); + $this->assertEquals($expectedData, $this->paymentProfile->getTokenFormData()); + } + + /** + * @return array + */ + public function getTokenFormDataProvider() + { + return [ + 'Without address' => [ + 'isNew' => true, + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CUSTOMER_EMAIL => '125', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::GATEWAY => 'gateway', + PaymentProfileInterface::PAYMENT_METHOD_TYPE => 'card', + PaymentProfileInterface::PAYMENT_TOKEN => 'token', + PaymentProfileInterface::PAYMENT_VAULT => 'vault', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::STATUS => 'success', + PaymentProfileInterface::CREATED => '2016-12-12', + PaymentProfileInterface::UPDATED => '2016-12-12', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::MAGENTO_ADDRESS_ID => '23', + ] + ], + 'billingData' => [AddressInterface::MAGENTO_ADDRESS_ID => '23'], + 'expectedData' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + ], + 'billingFormData' => [] + ], + 'With address' => [ + 'isNew' => true, + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CUSTOMER_EMAIL => '125', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::GATEWAY => 'gateway', + PaymentProfileInterface::PAYMENT_METHOD_TYPE => 'card', + PaymentProfileInterface::PAYMENT_TOKEN => 'token', + PaymentProfileInterface::PAYMENT_VAULT => 'vault', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::STATUS => 'success', + PaymentProfileInterface::CREATED => '2016-12-12', + PaymentProfileInterface::UPDATED => '2016-12-12', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + 'billingData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + 'expectedData' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + 'billingFormData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + ]; + } + + /** + * @param bool $isNew + * @param array $data + * @param array $billingData + * @param array $expectedData + * @param array $billingFormData + * @dataProvider getThirdPartyTokenFormDataProvider + */ + public function testGetThirdPartyTokenFormData($isNew, $data, $billingData, $expectedData, $billingFormData) + { + $this->billingAddressMock->expects($this->atLeastOnce()) + ->method('importData') + ->with($billingData) + ->willReturnSelf(); + + $this->billingAddressMock->expects($this->once()) + ->method('getAsChildFormData') + ->with($isNew) + ->willReturn($billingFormData); + + $this->paymentProfile->importData($data); + $this->assertEquals($expectedData, $this->paymentProfile->getThirdPartyTokenFormData()); + } + + /** + * @return array + */ + public function getThirdPartyTokenFormDataProvider() + { + return [ + 'Without address' => [ + 'isNew' => true, + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CUSTOMER_EMAIL => '125', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::GATEWAY => 'gateway', + PaymentProfileInterface::PAYMENT_METHOD_TYPE => 'card', + PaymentProfileInterface::PAYMENT_TOKEN => 'token', + PaymentProfileInterface::PAYMENT_VAULT => 'vault', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::STATUS => 'success', + PaymentProfileInterface::CREATED => '2016-12-12', + PaymentProfileInterface::UPDATED => '2016-12-12', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::MAGENTO_ADDRESS_ID => '23', + ] + ], + 'billingData' => [ + AddressInterface::MAGENTO_ADDRESS_ID => '23', + ], + 'expectedData' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + ], + 'billingFormData' => [] + ], + 'With address' => [ + 'isNew' => true, + 'data' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::MAGENTO_CUSTOMER_ID => '124', + PaymentProfileInterface::CUSTOMER_EMAIL => '125', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_NUMBER => '4111 1111 1111 1111', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_VERIFICATION_VALUE => 123, + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::GATEWAY => 'gateway', + PaymentProfileInterface::PAYMENT_METHOD_TYPE => 'card', + PaymentProfileInterface::PAYMENT_TOKEN => 'token', + PaymentProfileInterface::PAYMENT_VAULT => 'vault', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::STATUS => 'success', + PaymentProfileInterface::CREATED => '2016-12-12', + PaymentProfileInterface::UPDATED => '2016-12-12', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + 'billingData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + 'expectedData' => [ + PaymentProfileInterface::CUSTOMER_ID => '123', + PaymentProfileInterface::THIRD_PARTY_VAULT_TYPE => 'type', + PaymentProfileInterface::THIRD_PARTY_PAYMENT_TOKEN => 'token', + PaymentProfileInterface::CREDITCARD_TYPE => 'visa', + PaymentProfileInterface::CREDITCARD_FIRST_DIGITS => '411', + PaymentProfileInterface::CREDITCARD_LAST_DIGITS => '111111', + PaymentProfileInterface::CREDITCARD_MONTH => '04', + PaymentProfileInterface::CREDITCARD_YEAR => '2018', + PaymentProfileInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + 'billingFormData' => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ] + ], + ]; + } +} diff --git a/tests/Service/Product/ProductServiceTest.php b/tests/Service/Product/ProductServiceTest.php new file mode 100644 index 0000000..39bc167 --- /dev/null +++ b/tests/Service/Product/ProductServiceTest.php @@ -0,0 +1,190 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->productFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->productService = new ProductService($this->httpClientMock, $this->productFactoryMock); + } + + public function testCreateProduct() + { + $productMock = $this->createProductMock(); + $productData = [ + ProductInterface::SKU => 'sku' + ]; + + $this->productFactoryMock->expects($this->once()) + ->method('create') + ->with($productData) + ->willReturn($productMock); + + $this->assertSame($productMock, $this->productService->createProduct($productData)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveProductIfProductIsNotValid() + { + $productMock = $this->createProductMock(); + $productMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->productService->saveProduct($productMock); + } + + /** + * @param string $url + * @param string $itemId + * @param bool $isNew + * @param array $formData + * @param array $resultData + * @dataProvider saveProductDataProvider + */ + public function testSaveProduct($url, $itemId, $isNew, $formData, $resultData) + { + $productMock = $this->createProductMock(); + $productMock->expects($this->once())->method('isValid')->willReturn(true); + $productMock->expects($this->once())->method('isNew')->willReturn($isNew); + $productMock->expects($this->once())->method('getFormData')->willReturn($formData); + $productMock->expects($this->any())->method('getId')->willReturn($itemId); + $productMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [ProductService::API_NAME_PRODUCT => $formData]) + ->willReturn([ProductService::API_NAME_PRODUCT => $resultData]); + + $this->assertSame($productMock, $this->productService->saveProduct($productMock)); + } + + /** + * @return array + */ + public function saveProductDataProvider() + { + return [ + 'Save new product' => [ + 'url' => '/services/v2/product.json', + 'itemId' => null, + 'isNew' => true, + 'formData' => [ProductInterface::NAME => 'product name'], + 'resultData' => [ProductInterface::ID => 11], + ], + 'Update existing product' => [ + 'url' => '/services/v2/products/22.json', + 'itemId' => 22, + 'isNew' => false, + 'formData' => [ProductInterface::NAME => 'product name'], + 'resultData' => [ProductInterface::ID => 22], + ], + ]; + } + + public function testLoadProduct() + { + $itemId = 111; + $itemData = [ProductInterface::ID => $itemId]; + $productMock = $this->createProductMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v2/products/{$itemId}.json") + ->willReturn([ProductService::API_NAME_PRODUCT => $itemData]); + + $this->productFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($productMock); + + $this->assertSame($productMock, $this->productService->loadProduct($itemId)); + } + + /** + * @param int $customerId + * @param array $filters + * @param array $itemsData + * @dataProvider loadProductsDataProvider + */ + public function testLoadProducts($customerId, $filters, $itemsData) + { + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with('/services/v2/products.json', $filters) + ->willReturn([ProductService::API_NAME_PRODUCTS => $itemsData]); + + $products = []; + $productFactoryMap = []; + foreach ($itemsData as $itemData) { + $product = $this->createProductMock(); + $productFactoryMap[] = [$itemData, $product]; + $products[] = $product; + } + $this->productFactoryMock->expects($this->exactly(count($itemsData))) + ->method('create') + ->willReturnMap($productFactoryMap); + + $this->assertSame($products, $this->productService->loadProducts($customerId)); + } + + /** + * @return array + */ + public function loadProductsDataProvider() + { + return [ + 'Loading without filter' => [ + 'sku' => null, + 'filters' => [], + 'itemsData' => [[ProductInterface::ID => 111], [ProductInterface::ID => 222]] + ], + 'Loading by sku' => [ + 'sku' => '122-aab', + 'filters' => [ProductInterface::SKU => '122-aab'], + 'itemsData' => [[ProductInterface::SKU => '122-aab', ProductInterface::ID => 333]] + ], + ]; + } + + /** + * @return \SubscribePro\Service\Product\ProductInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createProductMock() + { + return $this->getMockBuilder('SubscribePro\Service\Product\ProductInterface')->getMock(); + } +} diff --git a/tests/Service/Product/ProductTest.php b/tests/Service/Product/ProductTest.php new file mode 100644 index 0000000..6288e4a --- /dev/null +++ b/tests/Service/Product/ProductTest.php @@ -0,0 +1,192 @@ +product = new Product(); + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isValidDataProvider + */ + public function testIsValid($data, $isValid) + { + $this->product->importData($data); + $this->assertEquals($isValid, $this->product->isValid()); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return [ + 'Not valid: new: without sku' => [ + 'data' => [ + ProductInterface::NAME => 'name', + ProductInterface::PRICE => 123, + ], + 'isValid' => false + ], + 'Not valid: new: without name' => [ + 'data' => [ + ProductInterface::SKU => 'sku', + ProductInterface::PRICE => 123, + ], + 'isValid' => false + ], + 'Not valid: new: without price' => [ + 'data' => [ + ProductInterface::SKU => 'sku', + ProductInterface::NAME => 'name', + ], + 'isValid' => false + ], + 'Valid: new' => [ + 'data' => [ + ProductInterface::SKU => 'sku', + ProductInterface::NAME => 'name', + ProductInterface::PRICE => 123, + ], + 'isValid' => true + ], + 'Valid: not new' => [ + 'data' => [ + ProductInterface::ID => 123, + ], + 'isValid' => true + ], + ]; + } + + /** + * @param array $data + * @param array $expectedData + * @dataProvider getFormDataProvider + */ + public function testGetFormData($data, $expectedData) + { + $this->product->importData($data); + $this->assertEquals($expectedData, $this->product->getFormData()); + } + + /** + * @return array + */ + public function getFormDataProvider() + { + return [ + 'New product' => [ + 'data' => [ + ProductInterface::SKU => 'sku', + ProductInterface::NAME => 'name', + ProductInterface::PRICE => 222, + ProductInterface::SHOW_ON_UI => false, + ProductInterface::MIN_QTY => '1', + ProductInterface::MAX_QTY => '22', + ProductInterface::DISCOUNT => 11, + ProductInterface::IS_DISCOUNT_PERCENTAGE => false, + ProductInterface::SUBSCRIPTION_OPTION_MODE => 'subscription_only', + ProductInterface::DEFAULT_SUBSCRIPTION_OPTION => 'subscribe', + ProductInterface::DEFAULT_INTERVAL => 'monthly', + ProductInterface::INTERVALS => [], + ProductInterface::PRODUCT_OPTIONS_MODE => 'mode', + ProductInterface::IS_TRIAL_PRODUCT => true, + ProductInterface::TRIAL_INTERVAL => 3, + ProductInterface::TRIAL_PRICE => 123, + ProductInterface::TRIAL_FULL_PRODUCT_SKU => 'sku', + ProductInterface::TRIAL_EMAIL_TEMPLATE_CODE => 'code', + ProductInterface::TRIAL_EMAIL_THRESHOLD_DAYS => 'days', + ProductInterface::TRIAL_WELCOME_EMAIL_TEMPLATE_CODE => 'welcome_code', + ProductInterface::IS_SUBSCRIPTION_ENABLED => true, + ProductInterface::CREATED => '2016-12-12', + ProductInterface::UPDATED => '2016-12-12', + ], + 'expectedData' => [ + ProductInterface::SKU => 'sku', + ProductInterface::NAME => 'name', + ProductInterface::SHOW_ON_UI => false, + ProductInterface::MIN_QTY => '1', + ProductInterface::MAX_QTY => '22', + ProductInterface::PRICE => 222, + ProductInterface::DISCOUNT => 11, + ProductInterface::IS_DISCOUNT_PERCENTAGE => false, + ProductInterface::SUBSCRIPTION_OPTION_MODE => 'subscription_only', + ProductInterface::DEFAULT_SUBSCRIPTION_OPTION => 'subscribe', + ProductInterface::DEFAULT_INTERVAL => 'monthly', + ProductInterface::INTERVALS => [], + ProductInterface::PRODUCT_OPTIONS_MODE => 'mode', + ProductInterface::IS_TRIAL_PRODUCT => true, + ProductInterface::TRIAL_INTERVAL => 3, + ProductInterface::TRIAL_PRICE => 123, + ProductInterface::TRIAL_FULL_PRODUCT_SKU => 'sku', + ProductInterface::TRIAL_EMAIL_TEMPLATE_CODE => 'code', + ProductInterface::TRIAL_EMAIL_THRESHOLD_DAYS => 'days', + ProductInterface::TRIAL_WELCOME_EMAIL_TEMPLATE_CODE => 'welcome_code', + ], + ], + 'Not new product' => [ + 'data' => [ + ProductInterface::ID => 333, + ProductInterface::SKU => 'sku', + ProductInterface::NAME => 'name', + ProductInterface::PRICE => 222, + ProductInterface::SHOW_ON_UI => false, + ProductInterface::MIN_QTY => '1', + ProductInterface::MAX_QTY => '22', + ProductInterface::DISCOUNT => 11, + ProductInterface::IS_DISCOUNT_PERCENTAGE => false, + ProductInterface::SUBSCRIPTION_OPTION_MODE => 'subscription_only', + ProductInterface::DEFAULT_SUBSCRIPTION_OPTION => 'subscribe', + ProductInterface::DEFAULT_INTERVAL => 'monthly', + ProductInterface::INTERVALS => [], + ProductInterface::PRODUCT_OPTIONS_MODE => 'mode', + ProductInterface::IS_TRIAL_PRODUCT => true, + ProductInterface::TRIAL_INTERVAL => 3, + ProductInterface::TRIAL_PRICE => 123, + ProductInterface::TRIAL_FULL_PRODUCT_SKU => 'sku', + ProductInterface::TRIAL_EMAIL_TEMPLATE_CODE => 'code', + ProductInterface::TRIAL_EMAIL_THRESHOLD_DAYS => 'days', + ProductInterface::TRIAL_WELCOME_EMAIL_TEMPLATE_CODE => 'welcome_code', + ProductInterface::IS_SUBSCRIPTION_ENABLED => true, + ProductInterface::CREATED => '2016-12-12', + ProductInterface::UPDATED => '2016-12-12', + ], + 'expectedData' => [ + ProductInterface::SKU => 'sku', + ProductInterface::NAME => 'name', + ProductInterface::SHOW_ON_UI => false, + ProductInterface::MIN_QTY => '1', + ProductInterface::MAX_QTY => '22', + ProductInterface::PRICE => 222, + ProductInterface::DISCOUNT => 11, + ProductInterface::IS_DISCOUNT_PERCENTAGE => false, + ProductInterface::SUBSCRIPTION_OPTION_MODE => 'subscription_only', + ProductInterface::DEFAULT_SUBSCRIPTION_OPTION => 'subscribe', + ProductInterface::DEFAULT_INTERVAL => 'monthly', + ProductInterface::INTERVALS => [], + ProductInterface::IS_TRIAL_PRODUCT => true, + ProductInterface::TRIAL_INTERVAL => 3, + ProductInterface::TRIAL_PRICE => 123, + ProductInterface::TRIAL_FULL_PRODUCT_SKU => 'sku', + ProductInterface::TRIAL_EMAIL_TEMPLATE_CODE => 'code', + ProductInterface::TRIAL_EMAIL_THRESHOLD_DAYS => 'days', + ProductInterface::TRIAL_WELCOME_EMAIL_TEMPLATE_CODE => 'welcome_code', + ] + ], + ]; + } +} diff --git a/tests/Service/Subscription/SubscriptionServiceTest.php b/tests/Service/Subscription/SubscriptionServiceTest.php new file mode 100644 index 0000000..c1c9d36 --- /dev/null +++ b/tests/Service/Subscription/SubscriptionServiceTest.php @@ -0,0 +1,232 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->subscriptionFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->subscriptionService = new SubscriptionService($this->httpClientMock, $this->subscriptionFactoryMock); + } + + public function testCreateSubscription() + { + $subscriptionMock = $this->createSubscriptionMock(); + $subscriptionData = [SubscriptionInterface::QTY => 555]; + + $this->subscriptionFactoryMock->expects($this->once()) + ->method('create') + ->with($subscriptionData) + ->willReturn($subscriptionMock); + + $this->assertSame($subscriptionMock, $this->subscriptionService->createSubscription($subscriptionData)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveSubscriptionIfSubscriptionIsNotValid() + { + $subscriptionMock = $this->createSubscriptionMock(); + $subscriptionMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->subscriptionService->saveSubscription($subscriptionMock); + } + + /** + * @param string $url + * @param string $itemId + * @param bool $isNew + * @param array $formData + * @param array $resultData + * @dataProvider saveSubscriptionDataProvider + */ + public function testSaveSubscription($url, $itemId, $isNew, $formData, $resultData) + { + $subscriptionMock = $this->createSubscriptionMock(); + $subscriptionMock->expects($this->once())->method('isValid')->willReturn(true); + $subscriptionMock->expects($this->once())->method('isNew')->willReturn($isNew); + $subscriptionMock->expects($this->once())->method('getFormData')->willReturn($formData); + $subscriptionMock->expects($this->any())->method('getId')->willReturn($itemId); + $subscriptionMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [SubscriptionService::API_NAME_SUBSCRIPTION => $formData]) + ->willReturn([SubscriptionService::API_NAME_SUBSCRIPTION => $resultData]); + + $this->assertSame($subscriptionMock, $this->subscriptionService->saveSubscription($subscriptionMock)); + } + + /** + * @return array + */ + public function saveSubscriptionDataProvider() + { + return [ + 'Save new subscription' => [ + 'url' => '/services/v2/subscription.json', + 'itemId' => null, + 'isNew' => true, + 'formData' => [SubscriptionInterface::QTY => '123'], + 'resultData' => [SubscriptionInterface::ID => 11], + ], + 'Update existing subscription' => [ + 'url' => '/services/v2/subscriptions/22.json', + 'itemId' => 22, + 'isNew' => false, + 'formData' => [SubscriptionInterface::QTY => '123'], + 'resultData' => [SubscriptionInterface::ID => 22], + ], + ]; + } + + public function testLoadSubscription() + { + $itemId = 111; + $subscriptionMock = $this->createSubscriptionMock(); + $itemData = [SubscriptionInterface::QTY => 123]; + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v2/subscriptions/{$itemId}.json") + ->willReturn([SubscriptionService::API_NAME_SUBSCRIPTION => $itemData]); + + $this->subscriptionFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($subscriptionMock); + + $this->assertSame($subscriptionMock, $this->subscriptionService->loadSubscription($itemId)); + } + + /** + * @param int $customerId + * @param array $filters + * @param array $itemsData + * @dataProvider loadSubscriptionsDataProvider + */ + public function testLoadSubscriptions($customerId, $filters, $itemsData) + { + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with('/services/v2/subscriptions.json', $filters) + ->willReturn([SubscriptionService::API_NAME_SUBSCRIPTIONS => $itemsData]); + + $subscriptions = []; + $subscriptionFactoryMap = []; + foreach ($itemsData as $itemData) { + $subscription = $this->createSubscriptionMock(); + $subscriptionFactoryMap[] = [$itemData, $subscription]; + $subscriptions[] = $subscription; + } + $this->subscriptionFactoryMock->expects($this->exactly(count($itemsData))) + ->method('create') + ->willReturnMap($subscriptionFactoryMap); + + $this->assertSame($subscriptions, $this->subscriptionService->loadSubscriptions($customerId)); + } + + /** + * @return array + */ + public function loadSubscriptionsDataProvider() + { + return [ + 'Loading without filter' => [ + 'customerId' => null, + 'filters' => [], + 'itemsData' => [[SubscriptionInterface::ID => 111], [SubscriptionInterface::ID => 222]] + ], + 'Loading by customer ID' => [ + 'customerId' => 122, + 'filters' => [SubscriptionInterface::CUSTOMER_ID => 122], + 'itemsData' => [[SubscriptionInterface::CUSTOMER_ID => 122, SubscriptionInterface::ID => 333]] + ], + ]; + } + + public function testCancelSubscription() + { + $subscriptionId = 17; + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v2/subscriptions/{$subscriptionId}/cancel.json"); + + $this->subscriptionService->cancelSubscription($subscriptionId); + } + + public function testPauseSubscription() + { + $subscriptionId = 17; + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v2/subscriptions/{$subscriptionId}/pause.json"); + + $this->subscriptionService->pauseSubscription($subscriptionId); + } + + public function testRestartSubscription() + { + $subscriptionId = 17; + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v2/subscriptions/{$subscriptionId}/restart.json"); + + $this->subscriptionService->restartSubscription($subscriptionId); + } + + public function testSkipSubscription() + { + $subscriptionId = 17; + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v2/subscriptions/{$subscriptionId}/skip.json"); + + $this->subscriptionService->skipSubscription($subscriptionId); + } + + /** + * @return \SubscribePro\Service\Subscription\SubscriptionInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createSubscriptionMock() + { + return $this->getMockBuilder('SubscribePro\Service\Subscription\SubscriptionInterface')->getMock(); + } +} diff --git a/tests/Service/Subscription/SubscriptionTest.php b/tests/Service/Subscription/SubscriptionTest.php new file mode 100644 index 0000000..fbe9eec --- /dev/null +++ b/tests/Service/Subscription/SubscriptionTest.php @@ -0,0 +1,654 @@ +paymentProfileMock = $this->createProfileMock(); + $this->shippingAddressMock = $this->createAddressMock(); + + $this->subscription = new Subscription([ + SubscriptionInterface::SHIPPING_ADDRESS => $this->shippingAddressMock, + SubscriptionInterface::PAYMENT_PROFILE => $this->paymentProfileMock + ]); + } + + /** + * @param array $data + * @param array $expectedAddressData + * @param int $addressId + * @param array $expectedPaymentProfileData + * @param int $profileId + * @param array $expectedData + * @dataProvider importDataDataProvider + */ + public function testImportData($data, $expectedAddressData, $addressId, $expectedPaymentProfileData, $profileId, $expectedData) + { + $this->paymentProfileMock->expects($this->once()) + ->method('importData') + ->with($expectedPaymentProfileData) + ->willReturnSelf(); + $this->paymentProfileMock->expects($this->once()) + ->method('toArray') + ->willReturn($expectedPaymentProfileData); + $this->paymentProfileMock->expects($this->any())->method('getId')->willReturn($profileId); + + $this->shippingAddressMock->expects($this->once()) + ->method('importData') + ->with($expectedAddressData) + ->willReturnSelf(); + $this->shippingAddressMock->expects($this->once()) + ->method('toArray') + ->willReturn($expectedAddressData); + $this->shippingAddressMock->expects($this->any())->method('getId')->willReturn($addressId); + + $this->subscription->importData($data); + $this->assertEquals($expectedData, $this->subscription->toArray()); + } + + /** + * @return array + */ + public function importDataDataProvider() + { + return [ + 'Empty data' => [ + 'data' => [], + 'expectedAddressData' => [], + 'shippingAddressId' => null, + 'expectedPaymentProfileData' => [], + 'paymentProfileId' => null, + 'expectedData' => [ + SubscriptionInterface::SHIPPING_ADDRESS => [], + SubscriptionInterface::PAYMENT_PROFILE => [], + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::PAYMENT_PROFILE_ID => null, + ] + ], + 'Shipping address and payment profile data are not array' => [ + 'data' => [ + SubscriptionInterface::SHIPPING_ADDRESS => 'invalid', + SubscriptionInterface::PAYMENT_PROFILE => 'invalid' + ], + 'expectedAddressData' => [], + 'shippingAddressId' => null, + 'expectedPaymentProfileData' => [], + 'paymentProfileId' => null, + 'expectedData' => [ + SubscriptionInterface::SHIPPING_ADDRESS => [], + SubscriptionInterface::PAYMENT_PROFILE => [], + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::PAYMENT_PROFILE_ID => null, + ] + ], + 'Shipping address data is array and payment profile data is array' => [ + 'data' => [ + SubscriptionInterface::SHIPPING_ADDRESS => [AddressInterface::CITY => 'city'], + SubscriptionInterface::PAYMENT_PROFILE => [PaymentProfileInterface::CREDITCARD_YEAR => 2016], + ], + 'expectedAddressData' => [AddressInterface::CITY => 'city'], + 'shippingAddressId' => null, + 'expectedPaymentProfileData' => [PaymentProfileInterface::CREDITCARD_YEAR => 2016], + 'paymentProfileId' => null, + 'expectedData' => [ + SubscriptionInterface::SHIPPING_ADDRESS => [AddressInterface::CITY => 'city'], + SubscriptionInterface::PAYMENT_PROFILE => [PaymentProfileInterface::CREDITCARD_YEAR => 2016], + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::PAYMENT_PROFILE_ID => null, + ] + ], + 'Shipping address data with ID and payment profile data with ID' => [ + 'data' => [ + SubscriptionInterface::SHIPPING_ADDRESS => [AddressInterface::ID => 112], + SubscriptionInterface::PAYMENT_PROFILE => [PaymentProfileInterface::ID => 113], + ], + 'expectedAddressData' => [AddressInterface::ID => 112], + 'shippingAddressId' => 112, + 'expectedPaymentProfileData' => [PaymentProfileInterface::ID => 113], + 'paymentProfileId' => 113, + 'expectedData' => [ + SubscriptionInterface::SHIPPING_ADDRESS_ID => 112, + SubscriptionInterface::SHIPPING_ADDRESS => [AddressInterface::ID => 112], + SubscriptionInterface::PAYMENT_PROFILE => [PaymentProfileInterface::ID => 113], + SubscriptionInterface::PAYMENT_PROFILE_ID => 113, + ] + ], + 'With shipping_address_id and payment_profile_id' => [ + 'data' => [ + SubscriptionInterface::SHIPPING_ADDRESS_ID => 200, + SubscriptionInterface::SHIPPING_ADDRESS => [AddressInterface::ID => 112], + SubscriptionInterface::PAYMENT_PROFILE_ID => 300, + SubscriptionInterface::PAYMENT_PROFILE => [PaymentProfileInterface::ID => 113], + ], + 'expectedAddressData' => [AddressInterface::ID => 112], + 'shippingAddressId' => 112, + 'expectedPaymentProfileData' => [PaymentProfileInterface::ID => 113], + 'paymentProfileId' => 113, + 'expectedData' => [ + SubscriptionInterface::SHIPPING_ADDRESS_ID => 200, + SubscriptionInterface::SHIPPING_ADDRESS => [AddressInterface::ID => 112], + SubscriptionInterface::PAYMENT_PROFILE => [PaymentProfileInterface::ID => 113], + SubscriptionInterface::PAYMENT_PROFILE_ID => 300, + ] + ], + ]; + } + + public function testImportDataWithAddressAndPaymentProfileInstances() + { + $subscriptionId = 111; + $addressId = 123; + $profileId = 131; + $addressMock = $this->createAddressMock(); + $addressMock->expects($this->once())->method('getId')->willReturn($addressId); + $profileMock = $this->createProfileMock(); + $profileMock->expects($this->once())->method('getId')->willReturn($profileId); + $data = [ + SubscriptionInterface::ID => $subscriptionId, + SubscriptionInterface::SHIPPING_ADDRESS => $addressMock, + SubscriptionInterface::PAYMENT_PROFILE => $profileMock, + ]; + + $this->subscription->importData($data); + $this->assertEquals($subscriptionId, $this->subscription->getId()); + $this->assertEquals($addressId, $this->subscription->getShippingAddressId()); + $this->assertEquals($profileId, $this->subscription->getPaymentProfileId()); + $this->assertSame($profileMock, $this->subscription->getPaymentProfile()); + $this->assertSame($addressMock, $this->subscription->getShippingAddress()); + } + + public function testToArray() + { + $addressData = [ + AddressInterface::CITY => 'city', + AddressInterface::COMPANY => 'company', + ]; + $paymentProfileData = [PaymentProfileInterface::CREDITCARD_YEAR => 2016]; + $expectedData = [ + SubscriptionInterface::ID => 111, + SubscriptionInterface::SHIPPING_ADDRESS => $addressData, + SubscriptionInterface::PAYMENT_PROFILE => $paymentProfileData, + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::PAYMENT_PROFILE_ID => null, + ]; + + $this->paymentProfileMock->expects($this->once()) + ->method('toArray') + ->willReturn($paymentProfileData); + $this->shippingAddressMock->expects($this->once()) + ->method('toArray') + ->willReturn($addressData); + + $this->subscription->setId(111); + $this->assertEquals($expectedData, $this->subscription->toArray()); + } + + public function testIsNotValidIfNotValidShippingAddress() + { + $addressData = [AddressInterface::COMPANY => 'company']; + $data = [ + SubscriptionInterface::CUSTOMER_ID => 11, + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS => $addressData + ]; + $this->paymentProfileMock->expects($this->once()) + ->method('importData') + ->with([]) + ->willReturnSelf(); + $this->paymentProfileMock->expects($this->once())->method('getId')->willReturn(123); + $this->shippingAddressMock->expects($this->once()) + ->method('importData') + ->with($addressData) + ->willReturnSelf(); + $this->shippingAddressMock->expects($this->once())->method('getId')->willReturn(null); + $this->shippingAddressMock->expects($this->once())->method('isAsChildValid')->willReturn(false); + + $this->subscription->importData($data); + $this->assertFalse($this->subscription->isValid()); + } + + /** + * @param array $data + * @param array $addressData + * @param bool $isNew + * @param bool $isValid + * @dataProvider isValidWithValidAddressDataProvider + */ + public function testIsValidWithValidAddress($data, $addressData, $isNew, $isValid) + { + $this->paymentProfileMock->expects($this->once()) + ->method('importData') + ->with([]) + ->willReturnSelf(); + $this->paymentProfileMock->expects($this->any())->method('getId')->willReturn(123); + + $this->shippingAddressMock->expects($this->once()) + ->method('importData') + ->with($addressData) + ->willReturnSelf(); + $this->shippingAddressMock->expects($this->once())->method('getId')->willReturn(null); + $this->shippingAddressMock->expects($this->once()) + ->method('isAsChildValid') + ->with($isNew) + ->willReturn(true); + + $this->subscription->importData($data); + $this->assertEquals($isValid, $this->subscription->isValid()); + } + + /** + * @return array + */ + public function isValidWithValidAddressDataProvider() + { + return [ + 'Not valid: new: empty data' => [ + 'data' => [], + 'addressData' => [], + 'isNew' => true, + 'isValid' => false + ], + 'Not valid: new: without use_fixed_price' => [ + 'data' => [ + SubscriptionInterface::CUSTOMER_ID => 11, + SubscriptionInterface::PAYMENT_PROFILE_ID => '04', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => '123', + SubscriptionInterface::INTERVAL => 'weekly', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value'] + ], + 'addressData' => ['key' => 'value'], + 'isNew' => true, + 'isValid' => false + ], + 'Not valid: new: without interval' => [ + 'data' => [ + SubscriptionInterface::CUSTOMER_ID => 11, + SubscriptionInterface::PAYMENT_PROFILE_ID => '04', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => '123', + SubscriptionInterface::USE_FIXED_PRICE => true, + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value'] + ], + 'addressData' => ['key' => 'value'], + 'isNew' => true, + 'isValid' => false + ], + 'Not valid: new: without customer ID' => [ + 'data' => [ + SubscriptionInterface::PAYMENT_PROFILE_ID => '04', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => '123', + SubscriptionInterface::USE_FIXED_PRICE => true, + SubscriptionInterface::INTERVAL => 'weekly', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value'] + ], + 'addressData' => ['key' => 'value'], + 'isNew' => true, + 'isValid' => false + ], + 'Valid: new' => [ + 'data' => [ + SubscriptionInterface::CUSTOMER_ID => 11, + SubscriptionInterface::PAYMENT_PROFILE_ID => '04', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => '123', + SubscriptionInterface::USE_FIXED_PRICE => true, + SubscriptionInterface::INTERVAL => 'weekly', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value'] + ], + 'addressData' => ['key' => 'value'], + 'isNew' => true, + 'isValid' => true + ], + 'Valid: not new: with shipping address' => [ + 'data' => [ + SubscriptionInterface::ID => 11, + SubscriptionInterface::PAYMENT_PROFILE_ID => '04', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => '123', + SubscriptionInterface::USE_FIXED_PRICE => true, + SubscriptionInterface::INTERVAL => 'weekly', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value'] + ], + 'addressData' => ['key' => 'value'], + 'isNew' => false, + 'isValid' => true + ], + ]; + } + + /** + * @param array $data + * @dataProvider isValidWithShippingAddressIdDataProvider + */ + public function testIsValidWithShippingAddressId($data) + { + $this->paymentProfileMock->expects($this->once()) + ->method('importData') + ->with([]) + ->willReturnSelf(); + + $this->shippingAddressMock->expects($this->once()) + ->method('importData') + ->with([]) + ->willReturnSelf(); + $this->shippingAddressMock->expects($this->never())->method('getId'); + $this->shippingAddressMock->expects($this->never())->method('isAsChildValid'); + + $this->subscription->importData($data); + $this->assertTrue($this->subscription->isValid()); + } + + /** + * @return array + */ + public function isValidWithShippingAddressIdDataProvider() + { + return [ + 'Valid: new: with shipping address ID' => [ + 'data' => [ + SubscriptionInterface::CUSTOMER_ID => 11, + SubscriptionInterface::PAYMENT_PROFILE_ID => '04', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => '123', + SubscriptionInterface::USE_FIXED_PRICE => true, + SubscriptionInterface::INTERVAL => 'weekly', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS_ID => 111, + ], + ], + 'Valid: not new: with shipping address ID' => [ + 'data' => [ + SubscriptionInterface::ID => 11, + SubscriptionInterface::PAYMENT_PROFILE_ID => '04', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => '123', + SubscriptionInterface::USE_FIXED_PRICE => true, + SubscriptionInterface::INTERVAL => 'weekly', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-12', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SHIPPING_ADDRESS_ID => 111, + ], + ], + ]; + } + + /** + * @param array $data + * @param array $addressData + * @param array $addressAsChildFormData + * @param array $expectedData + * @param bool $isNew + * @dataProvider getFormDataProvider + */ + public function testGetFormData($data, $addressData, $addressAsChildFormData, $expectedData, $isNew) + { + $this->paymentProfileMock->expects($this->once()) + ->method('importData') + ->with([]) + ->willReturnSelf(); + + $this->shippingAddressMock->expects($this->once()) + ->method('importData') + ->with($addressData) + ->willReturnSelf(); + $this->shippingAddressMock->expects($this->any())->method('getId')->willReturn(null); + $this->shippingAddressMock->expects($this->once()) + ->method('getAsChildFormData') + ->with($isNew) + ->willReturn($addressAsChildFormData); + + $this->subscription->importData($data); + $this->assertEquals($expectedData, $this->subscription->getFormData()); + } + + /** + * @return array + */ + public function getFormDataProvider() + { + return [ + 'New subscription with address' => [ + 'data' => [ + SubscriptionInterface::CUSTOMER_ID => '123', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::SUBSCRIPTION_PRODUCTS => [], + SubscriptionInterface::QTY => 123, + SubscriptionInterface::USE_FIXED_PRICE => false, + SubscriptionInterface::FIXED_PRICE => 222, + SubscriptionInterface::INTERVAL => 'monthly', + SubscriptionInterface::MAGENTO_STORE_CODE => 'code', + SubscriptionInterface::PAYMENT_PROFILE_ID => '333', + SubscriptionInterface::PAYMENT_PROFILE => [], + SubscriptionInterface::AUTHORIZE_NET_PAYMENT_PROFILE_ID => '313', + SubscriptionInterface::CREDITCARD_LAST_DIGITS => '311', + SubscriptionInterface::MAGENTO_BILLING_ADDRESS_ID => '5242', + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::MAGENTO_SHIPPING_ADDRESS_ID => '123', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SEND_CUSTOMER_NOTIFICATION_EMAIL => true, + SubscriptionInterface::FIRST_ORDER_ALREADY_CREATED => true, + SubscriptionInterface::STATUS => 'success', + SubscriptionInterface::COUPON_CODE => 'code', + SubscriptionInterface::USER_DEFINED_FIELDS => [], + SubscriptionInterface::RECURRING_ORDER_COUNT => 123, + SubscriptionInterface::LAST_ORDER_DATE => '2016-11-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-11', + SubscriptionInterface::EXPIRATION_DATE => '2016-11-11', + SubscriptionInterface::RETRY_AFTER => '2017-11-11', + SubscriptionInterface::CREATED => '2016-12-12', + SubscriptionInterface::UPDATED => '2016-12-12', + SubscriptionInterface::CANCELLED => '2016-10-12', + SubscriptionInterface::ERROR_TIME => '2014-12-12', + SubscriptionInterface::ERROR_CLASS => 'class', + SubscriptionInterface::ERROR_CLASS_DESCRIPTION => 'description', + SubscriptionInterface::ERROR_TYPE => 'type', + SubscriptionInterface::ERROR_MESSAGE => 'message', + SubscriptionInterface::FAILED_ORDER_ATTEMPT_COUNT => '2', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value', 'key2' => 'value2'] + ], + 'addressData' => ['key' => 'value', 'key2' => 'value2'], + 'addressAsChildFormData' => ['key' => 'value'], + 'expectedData' => [ + SubscriptionInterface::CUSTOMER_ID => '123', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => 123, + SubscriptionInterface::USE_FIXED_PRICE => false, + SubscriptionInterface::FIXED_PRICE => 222, + SubscriptionInterface::INTERVAL => 'monthly', + SubscriptionInterface::MAGENTO_STORE_CODE => 'code', + SubscriptionInterface::PAYMENT_PROFILE_ID => '333', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SEND_CUSTOMER_NOTIFICATION_EMAIL => true, + SubscriptionInterface::FIRST_ORDER_ALREADY_CREATED => true, + SubscriptionInterface::COUPON_CODE => 'code', + SubscriptionInterface::USER_DEFINED_FIELDS => [], + SubscriptionInterface::EXPIRATION_DATE => '2016-11-11', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-11', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value'] + ], + 'isNew' => true + ], + 'New subscription with shipping address ID' => [ + 'data' => [ + SubscriptionInterface::CUSTOMER_ID => '123', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::SUBSCRIPTION_PRODUCTS => [], + SubscriptionInterface::QTY => 123, + SubscriptionInterface::USE_FIXED_PRICE => false, + SubscriptionInterface::FIXED_PRICE => 222, + SubscriptionInterface::INTERVAL => 'monthly', + SubscriptionInterface::MAGENTO_STORE_CODE => 'code', + SubscriptionInterface::PAYMENT_PROFILE_ID => '333', + SubscriptionInterface::PAYMENT_PROFILE => [], + SubscriptionInterface::AUTHORIZE_NET_PAYMENT_PROFILE_ID => '313', + SubscriptionInterface::CREDITCARD_LAST_DIGITS => '311', + SubscriptionInterface::MAGENTO_BILLING_ADDRESS_ID => '5242', + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::MAGENTO_SHIPPING_ADDRESS_ID => '123', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SEND_CUSTOMER_NOTIFICATION_EMAIL => true, + SubscriptionInterface::FIRST_ORDER_ALREADY_CREATED => true, + SubscriptionInterface::STATUS => 'success', + SubscriptionInterface::COUPON_CODE => 'code', + SubscriptionInterface::USER_DEFINED_FIELDS => [], + SubscriptionInterface::RECURRING_ORDER_COUNT => 123, + SubscriptionInterface::LAST_ORDER_DATE => '2016-11-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-11', + SubscriptionInterface::EXPIRATION_DATE => '2016-11-11', + SubscriptionInterface::RETRY_AFTER => '2017-11-11', + SubscriptionInterface::CREATED => '2016-12-12', + SubscriptionInterface::UPDATED => '2016-12-12', + SubscriptionInterface::CANCELLED => '2016-10-12', + SubscriptionInterface::ERROR_TIME => '2014-12-12', + SubscriptionInterface::ERROR_CLASS => 'class', + SubscriptionInterface::ERROR_CLASS_DESCRIPTION => 'description', + SubscriptionInterface::ERROR_TYPE => 'type', + SubscriptionInterface::ERROR_MESSAGE => 'message', + SubscriptionInterface::FAILED_ORDER_ATTEMPT_COUNT => '2', + SubscriptionInterface::SHIPPING_ADDRESS_ID => 242, + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value', 'key2' => 'value2'] + ], + 'addressData' => ['key' => 'value', 'key2' => 'value2'], + 'addressAsChildFormData' => ['key' => 'value'], + 'expectedData' => [ + SubscriptionInterface::CUSTOMER_ID => '123', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => 123, + SubscriptionInterface::USE_FIXED_PRICE => false, + SubscriptionInterface::FIXED_PRICE => 222, + SubscriptionInterface::INTERVAL => 'monthly', + SubscriptionInterface::MAGENTO_STORE_CODE => 'code', + SubscriptionInterface::PAYMENT_PROFILE_ID => '333', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SEND_CUSTOMER_NOTIFICATION_EMAIL => true, + SubscriptionInterface::FIRST_ORDER_ALREADY_CREATED => true, + SubscriptionInterface::COUPON_CODE => 'code', + SubscriptionInterface::USER_DEFINED_FIELDS => [], + SubscriptionInterface::EXPIRATION_DATE => '2016-11-11', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-11', + SubscriptionInterface::SHIPPING_ADDRESS_ID => 242, + ], + 'isNew' => true + ], + 'Not new subscription with shipping address' => [ + 'data' => [ + SubscriptionInterface::ID => '151', + SubscriptionInterface::CUSTOMER_ID => '123', + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::SUBSCRIPTION_PRODUCTS => [], + SubscriptionInterface::QTY => 123, + SubscriptionInterface::USE_FIXED_PRICE => false, + SubscriptionInterface::FIXED_PRICE => 222, + SubscriptionInterface::INTERVAL => 'monthly', + SubscriptionInterface::MAGENTO_STORE_CODE => 'code', + SubscriptionInterface::PAYMENT_PROFILE_ID => '333', + SubscriptionInterface::PAYMENT_PROFILE => [], + SubscriptionInterface::AUTHORIZE_NET_PAYMENT_PROFILE_ID => '313', + SubscriptionInterface::CREDITCARD_LAST_DIGITS => '311', + SubscriptionInterface::MAGENTO_BILLING_ADDRESS_ID => '5242', + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::MAGENTO_SHIPPING_ADDRESS_ID => '123', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SEND_CUSTOMER_NOTIFICATION_EMAIL => true, + SubscriptionInterface::FIRST_ORDER_ALREADY_CREATED => true, + SubscriptionInterface::STATUS => 'success', + SubscriptionInterface::COUPON_CODE => 'code', + SubscriptionInterface::USER_DEFINED_FIELDS => [], + SubscriptionInterface::RECURRING_ORDER_COUNT => 123, + SubscriptionInterface::LAST_ORDER_DATE => '2016-11-12', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-11', + SubscriptionInterface::EXPIRATION_DATE => '2016-11-11', + SubscriptionInterface::RETRY_AFTER => '2017-11-11', + SubscriptionInterface::CREATED => '2016-12-12', + SubscriptionInterface::UPDATED => '2016-12-12', + SubscriptionInterface::CANCELLED => '2016-10-12', + SubscriptionInterface::ERROR_TIME => '2014-12-12', + SubscriptionInterface::ERROR_CLASS => 'class', + SubscriptionInterface::ERROR_CLASS_DESCRIPTION => 'description', + SubscriptionInterface::ERROR_TYPE => 'type', + SubscriptionInterface::ERROR_MESSAGE => 'message', + SubscriptionInterface::FAILED_ORDER_ATTEMPT_COUNT => '2', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value', 'key2' => 'value2'] + ], + 'addressData' => ['key' => 'value', 'key2' => 'value2'], + 'addressAsChildFormData' => ['key' => 'value'], + 'expectedData' => [ + SubscriptionInterface::PRODUCT_SKU => 'sku', + SubscriptionInterface::QTY => 123, + SubscriptionInterface::USE_FIXED_PRICE => false, + SubscriptionInterface::FIXED_PRICE => 222, + SubscriptionInterface::INTERVAL => 'monthly', + SubscriptionInterface::MAGENTO_STORE_CODE => 'code', + SubscriptionInterface::PAYMENT_PROFILE_ID => '333', + SubscriptionInterface::MAGENTO_SHIPPING_METHOD_CODE => 'tablerate', + SubscriptionInterface::SEND_CUSTOMER_NOTIFICATION_EMAIL => true, + SubscriptionInterface::COUPON_CODE => 'code', + SubscriptionInterface::USER_DEFINED_FIELDS => [], + SubscriptionInterface::EXPIRATION_DATE => '2016-11-11', + SubscriptionInterface::NEXT_ORDER_DATE => '2016-12-11', + SubscriptionInterface::SHIPPING_ADDRESS => ['key' => 'value'] + ], + 'isNew' => false + ], + ]; + } + + /** + * @return \SubscribePro\Service\PaymentProfile\PaymentProfileInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createProfileMock() + { + return $this->getMockBuilder('SubscribePro\Service\PaymentProfile\PaymentProfileInterface')->getMock(); + } + + /** + * @return \SubscribePro\Service\Address\AddressInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createAddressMock() + { + return $this->getMockBuilder('SubscribePro\Service\Address\AddressInterface')->getMock(); + } +} diff --git a/tests/Service/Token/TokenServiceTest.php b/tests/Service/Token/TokenServiceTest.php new file mode 100644 index 0000000..ed9fc71 --- /dev/null +++ b/tests/Service/Token/TokenServiceTest.php @@ -0,0 +1,119 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->tokenFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->tokenService = new TokenService($this->httpClientMock, $this->tokenFactoryMock); + } + + public function testCreateToken() + { + $tokenMock = $this->createTokenMock(); + $tokenData = [ + TokenInterface::CITY => 'city', + TokenInterface::TOKEN => 'token', + ]; + + $this->tokenFactoryMock->expects($this->once()) + ->method('create') + ->with($tokenData) + ->willReturn($tokenMock); + + $this->assertSame($tokenMock, $this->tokenService->createToken($tokenData)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToSaveTokenIfTokenIsNotValid() + { + $tokenMock = $this->createTokenMock(); + $tokenMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->tokenService->saveToken($tokenMock); + } + + public function testSaveToken() + { + $url = '/services/v1/vault/token.json'; + $formData = [TokenInterface::CITY => 'city']; + $expectedImportData = [TokenInterface::TOKEN => 'token']; + + $tokenMock = $this->createTokenMock(); + $tokenMock->expects($this->once())->method('isValid')->willReturn(true); + $tokenMock->expects($this->once())->method('getFormData')->willReturn($formData); + $tokenMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [TokenService::API_NAME_TOKEN => $formData]) + ->willReturn([TokenService::API_NAME_TOKEN => $expectedImportData]); + + $this->assertSame($tokenMock, $this->tokenService->saveToken($tokenMock)); + } + + public function testLoadToken() + { + $tokenValue = 'token'; + $itemData = [ + TokenInterface::TOKEN => 'token', + TokenInterface::CITY => 'city', + ]; + $tokenMock = $this->createTokenMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v1/vault/tokens/{$tokenValue}.json") + ->willReturn([TokenService::API_NAME_TOKEN => $itemData]); + + $this->tokenFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($tokenMock); + + $this->assertSame($tokenMock, $this->tokenService->loadToken($tokenValue)); + } + + /** + * @return \SubscribePro\Service\Token\TokenInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createTokenMock() + { + return $this->getMockBuilder('SubscribePro\Service\Token\TokenInterface')->getMock(); + } +} diff --git a/tests/Service/Token/TokenTest.php b/tests/Service/Token/TokenTest.php new file mode 100644 index 0000000..f812a8e --- /dev/null +++ b/tests/Service/Token/TokenTest.php @@ -0,0 +1,165 @@ +token = new Token(); + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isValidDataProvider + */ + public function testIsValid($data, $isValid) + { + $this->token->importData($data); + $this->assertEquals($isValid, $this->token->isValid()); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return [ + 'Not valid: empty data' => [ + 'data' => [], + 'isValid' => false + ], + 'Not valid: without month' => [ + 'data' => [ + TokenInterface::NUMBER => 11, + TokenInterface::YEAR => 2024, + TokenInterface::FIRST_NAME => 'name', + TokenInterface::LAST_NAME => 'surname', + TokenInterface::ADDRESS1 => 'address', + TokenInterface::CITY => 'city', + TokenInterface::COUNTRY => 'country', + TokenInterface::ZIP => 'zip', + TokenInterface::STATE => 'state', + ], + 'isValid' => false + ], + 'Not valid: without address' => [ + 'data' => [ + TokenInterface::NUMBER => 11, + TokenInterface::MONTH => 04, + TokenInterface::YEAR => 2024, + TokenInterface::FIRST_NAME => 'name', + TokenInterface::LAST_NAME => 'surname', + TokenInterface::CITY => 'city', + TokenInterface::COUNTRY => 'country', + TokenInterface::ZIP => 'zip', + TokenInterface::STATE => 'state', + ], + 'isValid' => false + ], + 'Not valid: without number' => [ + 'data' => [ + TokenInterface::MONTH => 04, + TokenInterface::YEAR => 2024, + TokenInterface::FIRST_NAME => 'name', + TokenInterface::LAST_NAME => 'surname', + TokenInterface::ADDRESS1 => 'address', + TokenInterface::CITY => 'city', + TokenInterface::COUNTRY => 'country', + TokenInterface::ZIP => 'zip', + TokenInterface::STATE => 'state', + ], + 'isValid' => false + ], + 'Not valid: without first name' => [ + 'data' => [ + TokenInterface::NUMBER => 11, + TokenInterface::MONTH => 04, + TokenInterface::YEAR => 2024, + TokenInterface::LAST_NAME => 'surname', + TokenInterface::ADDRESS1 => 'address', + TokenInterface::CITY => 'city', + TokenInterface::COUNTRY => 'country', + TokenInterface::ZIP => 'zip', + TokenInterface::STATE => 'state', + ], + 'isValid' => false + ], + 'Valid' => [ + 'data' => [ + TokenInterface::NUMBER => 11, + TokenInterface::MONTH => 04, + TokenInterface::YEAR => 2024, + TokenInterface::FIRST_NAME => 'name', + TokenInterface::LAST_NAME => 'surname', + TokenInterface::ADDRESS1 => 'address', + TokenInterface::CITY => 'city', + TokenInterface::COUNTRY => 'country', + TokenInterface::ZIP => 'zip', + TokenInterface::STATE => 'state', + ], + 'isValid' => true + ], + ]; + } + + public function testGetFormData() + { + $data = [ + TokenInterface::TOKEN => 'token', + TokenInterface::PAYMENT_METHOD_TYPE => 'type', + TokenInterface::CARD_TYPE => 'card type', + TokenInterface::NUMBER => '4111 1111 1111 1111', + TokenInterface::LAST_FOUR_DIGITS => '1111', + TokenInterface::FIRST_SIX_DIGITS => '411111', + TokenInterface::VERIFICATION_VALUE => 123, + TokenInterface::MONTH => '04', + TokenInterface::YEAR => '2019', + TokenInterface::FIRST_NAME => 'first name', + TokenInterface::LAST_NAME => 'last name', + TokenInterface::FULL_NAME => 'full name', + TokenInterface::COMPANY => 'company', + TokenInterface::ADDRESS1 => 'address', + TokenInterface::ADDRESS2 => 'address', + TokenInterface::CITY => 'city', + TokenInterface::STATE => 'state', + TokenInterface::ZIP => 'zip', + TokenInterface::COUNTRY => 'country', + TokenInterface::PHONE => 'phone', + TokenInterface::ELIGIBLE_FOR_CARD_UPDATER => true, + TokenInterface::STORAGE_STATE => 'ready', + TokenInterface::TEST => 'test', + TokenInterface::FINGERPRINT => 'fingerprint', + TokenInterface::CREATED_AT => '2016-12-12', + TokenInterface::UPDATED_AT => '2016-12-12', + ]; + $expectedData = [ + TokenInterface::NUMBER => '4111 1111 1111 1111', + TokenInterface::VERIFICATION_VALUE => 123, + TokenInterface::MONTH => '04', + TokenInterface::YEAR => '2019', + TokenInterface::FIRST_NAME => 'first name', + TokenInterface::LAST_NAME => 'last name', + TokenInterface::COMPANY => 'company', + TokenInterface::ADDRESS1 => 'address', + TokenInterface::ADDRESS2 => 'address', + TokenInterface::CITY => 'city', + TokenInterface::STATE => 'state', + TokenInterface::ZIP => 'zip', + TokenInterface::COUNTRY => 'country', + TokenInterface::PHONE => 'phone', + ]; + + $this->token->importData($data); + $this->assertEquals($expectedData, $this->token->getFormData()); + } +} diff --git a/tests/Service/Transaction/TransactionServiceTest.php b/tests/Service/Transaction/TransactionServiceTest.php new file mode 100644 index 0000000..9dccd0a --- /dev/null +++ b/tests/Service/Transaction/TransactionServiceTest.php @@ -0,0 +1,598 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->transactionFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->transactionService = new TransactionService($this->httpClientMock, $this->transactionFactoryMock); + } + + public function testCreateTransaction() + { + $transactionMock = $this->createTransactionMock(); + $transactionData = [ + TransactionInterface::ID => 123 + ]; + + $this->transactionFactoryMock->expects($this->once()) + ->method('create') + ->with($transactionData) + ->willReturn($transactionMock); + + $this->assertSame($transactionMock, $this->transactionService->createTransaction($transactionData)); + } + + public function testLoadTransaction() + { + $itemId = 111; + $itemData = [TransactionInterface::ID => $itemId]; + $transactionMock = $this->createTransactionMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v1/vault/transactions/{$itemId}.json") + ->willReturn([TransactionService::API_NAME_TRANSACTION => $itemData]); + + $this->transactionFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($transactionMock); + + $this->assertSame($transactionMock, $this->transactionService->loadTransaction($itemId)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToVerifyProfileIfNotValid() + { + $paymentProfileId = 1234; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once()) + ->method('isVerifyDataValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->transactionService->verifyProfile($paymentProfileId, $transactionMock); + } + + public function testVerifyProfile() + { + $paymentProfileId = 1234; + $formData = [TransactionInterface::AMOUNT => '1230']; + $expectedImportData = [TransactionInterface::ID => '111']; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once())->method('isVerifyDataValid')->willReturn(true); + $transactionMock->expects($this->once())->method('getVerifyFormData')->willReturn($formData); + $transactionMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v1/vault/paymentprofiles/{$paymentProfileId}/verify.json", [TransactionService::API_NAME_TRANSACTION => $formData]) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $expectedImportData]); + + $this->assertSame($transactionMock, $this->transactionService->verifyProfile($paymentProfileId, $transactionMock)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToAuthorizeByProfileIfNotValid() + { + $paymentProfileId = 1234; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->transactionService->authorizeByProfile($paymentProfileId, $transactionMock); + } + + public function testAuthorizeByProfile() + { + $paymentProfileId = 1234; + $formData = [TransactionInterface::AMOUNT => '1230']; + $expectedImportData = [TransactionInterface::ID => '111']; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once())->method('isValid')->willReturn(true); + $transactionMock->expects($this->once())->method('getFormData')->willReturn($formData); + $transactionMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v1/vault/paymentprofiles/{$paymentProfileId}/authorize.json", [TransactionService::API_NAME_TRANSACTION => $formData]) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $expectedImportData]); + + $this->assertSame($transactionMock, $this->transactionService->authorizeByProfile($paymentProfileId, $transactionMock)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Not all required fields are set. + */ + public function testFailToPurchaseByProfileIfNotValid() + { + $paymentProfileId = 1234; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once()) + ->method('isValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->transactionService->purchaseByProfile($paymentProfileId, $transactionMock); + } + + public function testPurchaseByProfile() + { + $paymentProfileId = 1234; + $formData = [TransactionInterface::AMOUNT => '1230']; + $expectedImportData = [TransactionInterface::ID => '111']; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once())->method('isValid')->willReturn(true); + $transactionMock->expects($this->once())->method('getFormData')->willReturn($formData); + $transactionMock->expects($this->once()) + ->method('importData') + ->with($expectedImportData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v1/vault/paymentprofiles/{$paymentProfileId}/purchase.json", [TransactionService::API_NAME_TRANSACTION => $formData]) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $expectedImportData]); + + $this->assertSame($transactionMock, $this->transactionService->purchaseByProfile($paymentProfileId, $transactionMock)); + } + + /** + * @param \PHPUnit_Framework_MockObject_MockObject $addressMock + * @param \PHPUnit_Framework_MockObject_MockObject|null $addressParameter + * @param string $token + * @param bool $isValidTransaction + * @param bool $isValidAddress + * @param string $expectedMessage + * @dataProvider failToAuthorizeByTokenIfNotValidDataProvider + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + */ + public function testFailToAuthorizeByTokenIfNotValid($addressMock, $addressParameter, $token, $isValidTransaction, $isValidAddress, $expectedMessage) + { + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once()) + ->method('isTokenDataValid') + ->willReturn($isValidTransaction); + + $addressMock->expects($this->any()) + ->method('isAsChildValid') + ->willReturn($isValidAddress); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->expectExceptionMessage($expectedMessage); + + $this->transactionService->authorizeByToken($token, $transactionMock, $addressParameter); + } + + /** + * @return array + */ + public function failToAuthorizeByTokenIfNotValidDataProvider() + { + $address1Mock = $this->createAddressMock(); + $address2Mock = $this->createAddressMock(); + $address3Mock = $this->createAddressMock(); + + return [ + 'Not valid transaction without address' => [ + 'addressMock' => $address1Mock, + 'addressParameter' => null, + 'token' => 'token', + 'isValidTransaction' => false, + 'isValidAddress' => false, + 'expectedMessage' => 'Not all required Transaction fields are set.', + ], + 'Not valid transaction with address' => [ + 'addressMock' => $address2Mock, + 'addressParameter' => $address2Mock, + 'token' => 'token', + 'isValidTransaction' => false, + 'isValidAddress' => true, + 'expectedMessage' => 'Not all required Transaction fields are set.', + ], + 'Not valid address' => [ + 'addressMock' => $address3Mock, + 'addressParameter' => $address3Mock, + 'token' => 'token', + 'isValidTransaction' => true, + 'isValidAddress' => false, + 'expectedMessage' => 'Not all required Address fields are set.', + ], + ]; + } + + /** + * @param \PHPUnit_Framework_MockObject_MockObject $addressMock + * @param \PHPUnit_Framework_MockObject_MockObject|null $addressParameter + * @param array $formData + * @param string $token + * @param string $url + * @param array $resultData + * @dataProvider authorizeByTokenDataProvider + */ + public function testAuthorizeByToken($addressMock, $addressParameter, $formData, $token, $url, $resultData) + { + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once())->method('isTokenDataValid')->willReturn(true); + $transactionMock->expects($this->once()) + ->method('getTokenFormData') + ->with($addressParameter) + ->willReturn($formData); + $transactionMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $addressMock->expects($this->any())->method('isAsChildValid')->willReturn(true); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [TransactionService::API_NAME_TRANSACTION => $formData]) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $resultData]); + + $this->assertSame($transactionMock, $this->transactionService->authorizeByToken($token, $transactionMock, $addressParameter)); + } + + /** + * @return array + */ + public function authorizeByTokenDataProvider() + { + $address1Mock = $this->createAddressMock(); + $address2Mock = $this->createAddressMock(); + + return [ + 'Authorize without address' => [ + 'addressMock' => $address1Mock, + 'addressParameter' => null, + 'formData' => [TransactionInterface::AMOUNT => '1230'], + 'token' => 'token', + 'url' => '/services/v1/vault/tokens/token/authorize.json', + 'resultData' => [TransactionInterface::ID => '111'], + ], + 'Authorize with address' => [ + 'addressMock' => $address2Mock, + 'addressParameter' => $address2Mock, + 'formData' => [TransactionInterface::AMOUNT => '1230'], + 'token' => 'token', + 'url' => '/services/v1/vault/tokens/token/authorize.json', + 'resultData' => [TransactionInterface::ID => '111'], + ], + ]; + } + + /** + * @param \PHPUnit_Framework_MockObject_MockObject $addressMock + * @param \PHPUnit_Framework_MockObject_MockObject|null $addressParameter + * @param string $token + * @param bool $isValidTransaction + * @param bool $isValidAddress + * @param string $expectedMessage + * @dataProvider failToPurchaseByTokenIfNotValidDataProvider + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + */ + public function testFailToPurchaseByTokenIfNotValid($addressMock, $addressParameter, $token, $isValidTransaction, $isValidAddress, $expectedMessage) + { + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once()) + ->method('isTokenDataValid') + ->willReturn($isValidTransaction); + + $addressMock->expects($this->any()) + ->method('isAsChildValid') + ->willReturn($isValidAddress); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->expectExceptionMessage($expectedMessage); + + $this->transactionService->purchaseByToken($token, $transactionMock, $addressParameter); + } + + /** + * @return array + */ + public function failToPurchaseByTokenIfNotValidDataProvider() + { + $address1Mock = $this->createAddressMock(); + $address2Mock = $this->createAddressMock(); + $address3Mock = $this->createAddressMock(); + + return [ + 'Not valid transaction without address' => [ + 'addressMock' => $address1Mock, + 'addressParameter' => null, + 'token' => 'token', + 'isValidTransaction' => false, + 'isValidAddress' => false, + 'expectedMessage' => 'Not all required Transaction fields are set.', + ], + 'Not valid transaction with address' => [ + 'addressMock' => $address2Mock, + 'addressParameter' => $address2Mock, + 'token' => 'token', + 'isValidTransaction' => false, + 'isValidAddress' => true, + 'expectedMessage' => 'Not all required Transaction fields are set.', + ], + 'Not valid address' => [ + 'addressMock' => $address3Mock, + 'addressParameter' => $address3Mock, + 'token' => 'token', + 'isValidTransaction' => true, + 'isValidAddress' => false, + 'expectedMessage' => 'Not all required Address fields are set.', + ], + ]; + } + + /** + * @param \PHPUnit_Framework_MockObject_MockObject $addressMock + * @param \PHPUnit_Framework_MockObject_MockObject|null $addressParameter + * @param array $formData + * @param string $token + * @param string $url + * @param array $resultData + * @dataProvider purchaseByTokenDataProvider + */ + public function testPurchaseByToken($addressMock, $addressParameter, $formData, $token, $url, $resultData) + { + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once())->method('isTokenDataValid')->willReturn(true); + $transactionMock->expects($this->once()) + ->method('getTokenFormData') + ->with($addressParameter) + ->willReturn($formData); + $transactionMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $addressMock->expects($this->any()) + ->method('isAsChildValid') + ->willReturn(true); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [TransactionService::API_NAME_TRANSACTION => $formData]) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $resultData]); + + $this->assertSame($transactionMock, $this->transactionService->purchaseByToken($token, $transactionMock, $addressParameter)); + } + + /** + * @return array + */ + public function purchaseByTokenDataProvider() + { + $address1Mock = $this->createAddressMock(); + $address2Mock = $this->createAddressMock(); + + return [ + 'Purchase without address' => [ + 'addressMock' => $address1Mock, + 'addressParameter' => null, + 'formData' => [TransactionInterface::AMOUNT => '1230'], + 'token' => 'token', + 'url' => '/services/v1/vault/tokens/token/purchase.json', + 'resultData' => [TransactionInterface::ID => '111'], + ], + 'Purchase with address' => [ + 'addressMock' => $address2Mock, + 'addressParameter' => $address2Mock, + 'formData' => [TransactionInterface::AMOUNT => '1111'], + 'token' => 'token', + 'url' => '/services/v1/vault/tokens/token/purchase.json', + 'resultData' => [TransactionInterface::ID => '222'], + ], + ]; + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Currency code not specified for given amount. + */ + public function testFailToCaptureIfNotValid() + { + $transactionId = '121'; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once()) + ->method('isServiceDataValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->transactionService->capture($transactionId, $transactionMock); + } + + public function testCapture() + { + $transactionId = '121'; + $url = "/services/v1/vault/transactions/{$transactionId}/capture.json"; + $resultData = [TransactionInterface::ID => $transactionId]; + + $transactionMock = $this->createTransactionMock(); + $this->transactionFactoryMock->expects($this->once()) + ->method('create') + ->with($resultData) + ->willReturn($transactionMock); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, []) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $resultData]); + + $this->assertSame($transactionMock, $this->transactionService->capture($transactionId)); + } + + public function testCaptureWithTransaction() + { + $transactionId = '121'; + $url = "/services/v1/vault/transactions/{$transactionId}/capture.json"; + $formData = [TransactionInterface::AMOUNT => '1230']; + $resultData = [TransactionInterface::ID => $transactionId]; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once())->method('isServiceDataValid')->willReturn(true); + $transactionMock->expects($this->once())->method('getServiceFormData')->willReturn($formData); + $transactionMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [TransactionService::API_NAME_TRANSACTION => $formData]) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $resultData]); + + $this->assertSame($transactionMock, $this->transactionService->capture($transactionId, $transactionMock)); + } + + /** + * @expectedException \SubscribePro\Exception\EntityInvalidDataException + * @expectedExceptionMessage Currency code not specified for given amount. + */ + public function testFailToCreditIfNotValid() + { + $transactionId = '121'; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once()) + ->method('isServiceDataValid') + ->willReturn(false); + + $this->httpClientMock->expects($this->never())->method('post'); + + $this->transactionService->credit($transactionId, $transactionMock); + } + + public function testCredit() + { + $transactionId = '121'; + $url = "/services/v1/vault/transactions/{$transactionId}/credit.json"; + $resultData = [TransactionInterface::ID => $transactionId]; + + $transactionMock = $this->createTransactionMock(); + $this->transactionFactoryMock->expects($this->once()) + ->method('create') + ->with($resultData) + ->willReturn($transactionMock); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, []) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $resultData]); + + $this->assertSame($transactionMock, $this->transactionService->credit($transactionId)); + } + + public function testCreditWithTransaction() + { + $transactionId = '121'; + $url = "/services/v1/vault/transactions/{$transactionId}/credit.json"; + $formData = [TransactionInterface::AMOUNT => '1230']; + $resultData = [TransactionInterface::ID => $transactionId]; + + $transactionMock = $this->createTransactionMock(); + $transactionMock->expects($this->once())->method('isServiceDataValid')->willReturn(true); + $transactionMock->expects($this->once())->method('getServiceFormData')->willReturn($formData); + $transactionMock->expects($this->once()) + ->method('importData') + ->with($resultData) + ->willReturnSelf(); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with($url, [TransactionService::API_NAME_TRANSACTION => $formData]) + ->willReturn([TransactionService::API_NAME_TRANSACTION => $resultData]); + + $this->assertSame($transactionMock, $this->transactionService->credit($transactionId, $transactionMock)); + } + + public function testVoid() + { + $itemId = 111; + $resultData = [TransactionInterface::ID => $itemId]; + + $transactionMock = $this->createTransactionMock(); + $this->transactionFactoryMock->expects($this->once()) + ->method('create') + ->with($resultData) + ->willReturn($transactionMock); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with("/services/v1/vault/transactions/{$itemId}/void.json") + ->willReturn([TransactionService::API_NAME_TRANSACTION => $resultData]); + + $this->assertSame($transactionMock, $this->transactionService->void($itemId)); + } + + /** + * @return \SubscribePro\Service\Transaction\TransactionInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createTransactionMock() + { + return $this->getMockBuilder('SubscribePro\Service\Transaction\TransactionInterface')->getMock(); + } + + /** + * @return \SubscribePro\Service\Address\AddressInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createAddressMock() + { + return $this->getMockBuilder('SubscribePro\Service\Address\AddressInterface')->getMock(); + } +} diff --git a/tests/Service/Transaction/TransactionTest.php b/tests/Service/Transaction/TransactionTest.php new file mode 100644 index 0000000..4de9ea9 --- /dev/null +++ b/tests/Service/Transaction/TransactionTest.php @@ -0,0 +1,384 @@ +transaction = new Transaction(); + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isValidDataProvider + */ + public function testIsValid($data, $isValid) + { + $this->transaction->importData($data); + $this->assertEquals($isValid, $this->transaction->isValid()); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return $this->getIsValidData(); + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isVerifyDataValidDataProvider + */ + public function testIsVerifyDataValid($data, $isValid) + { + $this->transaction->importData($data); + $this->assertEquals($isValid, $this->transaction->isVerifyDataValid()); + } + + /** + * @return array + */ + public function isVerifyDataValidDataProvider() + { + return [ + 'Not valid: empty data' => [ + 'data' => [], + 'isValid' => false + ], + 'Valid' => [ + 'data' => [ + TransactionInterface::CURRENCY_CODE => 11, + ], + 'isValid' => true + ], + ]; + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isServiceDataValidDataProvider + */ + public function testIsServiceDataValid($data, $isValid) + { + $this->transaction->importData($data); + $this->assertEquals($isValid, $this->transaction->isServiceDataValid()); + } + + /** + * @return array + */ + public function isServiceDataValidDataProvider() + { + return $this->getIsValidData(); + } + + /** + * @param array $data + * @param bool $isValid + * @dataProvider isTokenDataValidDataProvider + */ + public function testIsTokenDataValid($data, $isValid) + { + $this->transaction->importData($data); + $this->assertEquals($isValid, $this->transaction->isTokenDataValid()); + } + + /** + * @return array + */ + public function isTokenDataValidDataProvider() + { + return $this->getIsValidData(); + } + + private function getIsValidData() + { + return [ + 'Not valid: empty data' => [ + 'data' => [], + 'isValid' => false + ], + 'Not valid: without currency code' => [ + 'data' => [ + TransactionInterface::AMOUNT => 11, + ], + 'isValid' => false + ], + 'Not valid: without amount' => [ + 'data' => [ + TransactionInterface::CURRENCY_CODE => 'UAH', + ], + 'isValid' => false + ], + 'Valid' => [ + 'data' => [ + TransactionInterface::CURRENCY_CODE => 'UAH', + TransactionInterface::AMOUNT => 123, + ], + 'isValid' => true + ], + ]; + } + + public function testGetFormData() + { + $data = [ + TransactionInterface::TOKEN => 'token', + TransactionInterface::GATEWAY_SPECIFIC_RESPONSE => 'response', + TransactionInterface::GATEWAY_TYPE => 'type', + TransactionInterface::AUTHORIZE_NET_RESPONSE_REASON_CODE => 'code', + TransactionInterface::SUBSCRIBE_PRO_ERROR_DESCRIPTION => 'description', + TransactionInterface::CREDITCARD_TYPE => 'type', + TransactionInterface::CREDITCARD_LAST_DIGITS => '1111', + TransactionInterface::CREDITCARD_FIRST_DIGITS => '411111', + TransactionInterface::CREDITCARD_MONTH => '04', + TransactionInterface::CREDITCARD_YEAR => '2019', + TransactionInterface::BILLING_ADDRESS => '123', + TransactionInterface::REF_PAYMENT_PROFILE_ID => '414', + TransactionInterface::REF_TRANSACTION_ID => '2323', + TransactionInterface::REF_GATEWAY_ID => '525', + TransactionInterface::REF_TOKEN => 'token', + TransactionInterface::TYPE => 'type', + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::STATE => 'state', + TransactionInterface::GATEWAY_TRANSACTION_ID => '124', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + TransactionInterface::RESPONSE_MESSAGE => 'message', + TransactionInterface::ERROR_CODE => 'error_code', + TransactionInterface::ERROR_DETAIL => 'detail', + TransactionInterface::CVV_CODE => 'cvv_code', + TransactionInterface::CVV_MESSAGE => 'cvv_message', + TransactionInterface::AVS_CODE => 'avs_code', + TransactionInterface::AVS_MESSAGE => 'avs_message', + TransactionInterface::SUBSCRIBE_PRO_ERROR_CLASS => 'class', + TransactionInterface::SUBSCRIBE_PRO_ERROR_TYPE => 'type', + TransactionInterface::CREATED => '2016-12-12', + ]; + $expectedData = [ + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + ]; + + $this->transaction->importData($data); + $this->assertEquals($expectedData, $this->transaction->getFormData()); + } + + public function testGetVerifyFormData() + { + $data = [ + TransactionInterface::TOKEN => 'token', + TransactionInterface::GATEWAY_SPECIFIC_RESPONSE => 'response', + TransactionInterface::GATEWAY_TYPE => 'type', + TransactionInterface::AUTHORIZE_NET_RESPONSE_REASON_CODE => 'code', + TransactionInterface::SUBSCRIBE_PRO_ERROR_DESCRIPTION => 'description', + TransactionInterface::CREDITCARD_TYPE => 'type', + TransactionInterface::CREDITCARD_LAST_DIGITS => '1111', + TransactionInterface::CREDITCARD_FIRST_DIGITS => '411111', + TransactionInterface::CREDITCARD_MONTH => '04', + TransactionInterface::CREDITCARD_YEAR => '2019', + TransactionInterface::BILLING_ADDRESS => '123', + TransactionInterface::REF_PAYMENT_PROFILE_ID => '414', + TransactionInterface::REF_TRANSACTION_ID => '2323', + TransactionInterface::REF_GATEWAY_ID => '525', + TransactionInterface::REF_TOKEN => 'token', + TransactionInterface::TYPE => 'type', + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::STATE => 'state', + TransactionInterface::GATEWAY_TRANSACTION_ID => '124', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + TransactionInterface::RESPONSE_MESSAGE => 'message', + TransactionInterface::ERROR_CODE => 'error_code', + TransactionInterface::ERROR_DETAIL => 'detail', + TransactionInterface::CVV_CODE => 'cvv_code', + TransactionInterface::CVV_MESSAGE => 'cvv_message', + TransactionInterface::AVS_CODE => 'avs_code', + TransactionInterface::AVS_MESSAGE => 'avs_message', + TransactionInterface::SUBSCRIBE_PRO_ERROR_CLASS => 'class', + TransactionInterface::SUBSCRIBE_PRO_ERROR_TYPE => 'type', + TransactionInterface::CREATED => '2016-12-12', + ]; + $expectedData = [ + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + ]; + + $this->transaction->importData($data); + $this->assertEquals($expectedData, $this->transaction->getVerifyFormData()); + } + + public function testGetServiceFormData() + { + $data = [ + TransactionInterface::TOKEN => 'token', + TransactionInterface::GATEWAY_SPECIFIC_RESPONSE => 'response', + TransactionInterface::GATEWAY_TYPE => 'type', + TransactionInterface::AUTHORIZE_NET_RESPONSE_REASON_CODE => 'code', + TransactionInterface::SUBSCRIBE_PRO_ERROR_DESCRIPTION => 'description', + TransactionInterface::CREDITCARD_TYPE => 'type', + TransactionInterface::CREDITCARD_LAST_DIGITS => '1111', + TransactionInterface::CREDITCARD_FIRST_DIGITS => '411111', + TransactionInterface::CREDITCARD_MONTH => '04', + TransactionInterface::CREDITCARD_YEAR => '2019', + TransactionInterface::BILLING_ADDRESS => '123', + TransactionInterface::REF_PAYMENT_PROFILE_ID => '414', + TransactionInterface::REF_TRANSACTION_ID => '2323', + TransactionInterface::REF_GATEWAY_ID => '525', + TransactionInterface::REF_TOKEN => 'token', + TransactionInterface::TYPE => 'type', + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::STATE => 'state', + TransactionInterface::GATEWAY_TRANSACTION_ID => '124', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + TransactionInterface::RESPONSE_MESSAGE => 'message', + TransactionInterface::ERROR_CODE => 'error_code', + TransactionInterface::ERROR_DETAIL => 'detail', + TransactionInterface::CVV_CODE => 'cvv_code', + TransactionInterface::CVV_MESSAGE => 'cvv_message', + TransactionInterface::AVS_CODE => 'avs_code', + TransactionInterface::AVS_MESSAGE => 'avs_message', + TransactionInterface::SUBSCRIBE_PRO_ERROR_CLASS => 'class', + TransactionInterface::SUBSCRIBE_PRO_ERROR_TYPE => 'type', + TransactionInterface::CREATED => '2016-12-12', + ]; + $expectedData = [ + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + ]; + + $this->transaction->importData($data); + $this->assertEquals($expectedData, $this->transaction->getServiceFormData()); + } + + /** + * @param \SubscribePro\Service\Address\AddressInterface|null $address + * @param array $expectedData + * @dataProvider getTokenFormDataTest + */ + public function testGetTokenFormData($address, $expectedData) + { + $data = [ + TransactionInterface::TOKEN => 'token', + TransactionInterface::GATEWAY_SPECIFIC_RESPONSE => 'response', + TransactionInterface::GATEWAY_TYPE => 'type', + TransactionInterface::AUTHORIZE_NET_RESPONSE_REASON_CODE => 'code', + TransactionInterface::SUBSCRIBE_PRO_ERROR_DESCRIPTION => 'description', + TransactionInterface::CREDITCARD_TYPE => 'type', + TransactionInterface::CREDITCARD_LAST_DIGITS => '1111', + TransactionInterface::CREDITCARD_FIRST_DIGITS => '411111', + TransactionInterface::CREDITCARD_MONTH => '04', + TransactionInterface::CREDITCARD_YEAR => '2019', + TransactionInterface::BILLING_ADDRESS => '123', + TransactionInterface::REF_PAYMENT_PROFILE_ID => '414', + TransactionInterface::REF_TRANSACTION_ID => '2323', + TransactionInterface::REF_GATEWAY_ID => '525', + TransactionInterface::REF_TOKEN => 'token', + TransactionInterface::TYPE => 'type', + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::STATE => 'state', + TransactionInterface::GATEWAY_TRANSACTION_ID => '124', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + TransactionInterface::RESPONSE_MESSAGE => 'message', + TransactionInterface::ERROR_CODE => 'error_code', + TransactionInterface::ERROR_DETAIL => 'detail', + TransactionInterface::CVV_CODE => 'cvv_code', + TransactionInterface::CVV_MESSAGE => 'cvv_message', + TransactionInterface::AVS_CODE => 'avs_code', + TransactionInterface::AVS_MESSAGE => 'avs_message', + TransactionInterface::SUBSCRIBE_PRO_ERROR_CLASS => 'class', + TransactionInterface::SUBSCRIBE_PRO_ERROR_TYPE => 'type', + TransactionInterface::CREATED => '2016-12-12', + ]; + + $this->transaction->importData($data); + $this->assertEquals($expectedData, $this->transaction->getTokenFormData($address)); + } + + public function getTokenFormDataTest() + { + return [ + 'Without address' => [ + 'address' => null, + 'expectedData' => [ + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + TransactionInterface::CREDITCARD_MONTH => '04', + TransactionInterface::CREDITCARD_YEAR => '2019', + ] + ], + 'With address' => [ + 'address' => new Address([ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MAGENTO_ADDRESS_ID => '23', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::CREATED => '2016-12-12', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ]), + 'expectedData' => [ + TransactionInterface::AMOUNT => 'amount', + TransactionInterface::CURRENCY_CODE => 'currency code', + TransactionInterface::EMAIL => 'email@example.com', + TransactionInterface::ORDER_ID => '123', + TransactionInterface::IP => '0.0.0.0', + TransactionInterface::CREDITCARD_MONTH => '04', + TransactionInterface::CREDITCARD_YEAR => '2019', + TransactionInterface::BILLING_ADDRESS => [ + AddressInterface::CITY => 'city', + AddressInterface::COUNTRY => 'country', + AddressInterface::COMPANY => 'company', + AddressInterface::FIRST_NAME => 'first name', + AddressInterface::LAST_NAME => 'last name', + AddressInterface::MIDDLE_NAME => 'middle name', + AddressInterface::PHONE => 'phone', + AddressInterface::POSTCODE => 'postcode', + AddressInterface::REGION => 'region', + AddressInterface::STREET1 => 'street1', + AddressInterface::STREET2 => 'street2', + ], + ] + ] + ]; + } +} diff --git a/tests/Service/Webhook/Destination/DestinationTest.php b/tests/Service/Webhook/Destination/DestinationTest.php new file mode 100644 index 0000000..54ac62b --- /dev/null +++ b/tests/Service/Webhook/Destination/DestinationTest.php @@ -0,0 +1,29 @@ + 444 + ]; + $destination = new Destination([ + DestinationInterface::ID => 11, + DestinationInterface::ENDPOINT => new Endpoint($endPointData), + ]); + + $expectedData = [ + DestinationInterface::ID => 11, + DestinationInterface::ENDPOINT => $endPointData, + ]; + + $this->assertEquals($expectedData, $destination->toArray()); + } +} diff --git a/tests/Service/Webhook/EventTest.php b/tests/Service/Webhook/EventTest.php new file mode 100644 index 0000000..5024a24 --- /dev/null +++ b/tests/Service/Webhook/EventTest.php @@ -0,0 +1,65 @@ + 123 + ]; + $subscriptionData = [ + SubscriptionInterface::QTY => 222, + SubscriptionInterface::PAYMENT_PROFILE => [ + PaymentProfileInterface::BILLING_ADDRESS => [] + ], + SubscriptionInterface::SHIPPING_ADDRESS => [], + SubscriptionInterface::SHIPPING_ADDRESS_ID => null, + SubscriptionInterface::PAYMENT_PROFILE_ID => null, + ]; + $subscription = new Subscription([ + SubscriptionInterface::QTY => 222, + SubscriptionInterface::PAYMENT_PROFILE => new PaymentProfile([ + PaymentProfileInterface::BILLING_ADDRESS => new Address() + ]), + SubscriptionInterface::SHIPPING_ADDRESS => new Address() + ]); + $endPointData = [ + EndpointInterface::ID => 444 + ]; + $event = new Event([ + EventInterface::ID => 111, + EventInterface::CUSTOMER => new Customer($customerData), + EventInterface::SUBSCRIPTION => $subscription, + EventInterface::DESTINATIONS => [new Destination([ + DestinationInterface::ENDPOINT => new Endpoint($endPointData) + ])], + ]); + + $expectedData = [ + EventInterface::ID => 111, + EventInterface::CUSTOMER => $customerData, + EventInterface::SUBSCRIPTION => $subscriptionData, + EventInterface::DESTINATIONS => [ + [DestinationInterface::ENDPOINT => $endPointData] + ], + ]; + + $this->assertEquals($expectedData, $event->toArray()); + } +} diff --git a/tests/Service/Webhook/WebhookServiceTest.php b/tests/Service/Webhook/WebhookServiceTest.php new file mode 100644 index 0000000..aadbd57 --- /dev/null +++ b/tests/Service/Webhook/WebhookServiceTest.php @@ -0,0 +1,76 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->eventFactoryMock = $this->getMockBuilder('SubscribePro\Service\DataFactoryInterface')->getMock(); + + $this->webhookService = new WebhookService($this->httpClientMock, $this->eventFactoryMock); + } + + public function testFailToPing() + { + $exception = new \SubscribePro\Exception\HttpException(new Response()); + + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with('/services/v2/webhook-test.json') + ->willThrowException($exception); + + $this->assertFalse($this->webhookService->ping()); + } + + public function testPing() + { + $this->httpClientMock->expects($this->once()) + ->method('post') + ->with('/services/v2/webhook-test.json'); + + $this->assertTrue($this->webhookService->ping()); + } + + public function testLoadEvent() + { + $eventId = 111; + $itemData = [EventInterface::ID => $eventId]; + $eventMock = $this->getMockBuilder('SubscribePro\Service\Webhook\EventInterface')->getMock(); + + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with("/services/v2/webhook-events/{$eventId}.json") + ->willReturn([WebhookService::API_NAME_WEBHOOK_EVENT => $itemData]); + + $this->eventFactoryMock->expects($this->once()) + ->method('create') + ->with($itemData) + ->willReturn($eventMock); + + $this->assertSame($eventMock, $this->webhookService->loadEvent($eventId)); + } +} diff --git a/tests/Tools/ConfigTest.php b/tests/Tools/ConfigTest.php new file mode 100644 index 0000000..6d3bf46 --- /dev/null +++ b/tests/Tools/ConfigTest.php @@ -0,0 +1,60 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->setMethods(['get']) + ->getMock(); + + $this->configTool = new Config($this->httpClientMock); + } + + /** + * @param array $responseData + * @param array $expectedResult + * @dataProvider loadDataProvider + */ + public function testLoad($responseData, $expectedResult) + { + $this->httpClientMock->expects($this->once()) + ->method('get') + ->with('/services/v2/config.json') + ->willReturn($responseData); + + $this->assertEquals($expectedResult, $this->configTool->load()); + } + + /** + * @return array + */ + public function loadDataProvider() + { + return [ + 'Not valid response' => [ + 'resultData' => ['some_key' => 'some_value'], + 'expectedResult' => [] + ], + 'Valid response' => [ + 'resultData' => [Config::API_NAME_CONFIG => ['config_option' => 'option_value']], + 'expectedResult' => ['config_option' => 'option_value'] + ], + ]; + } +} diff --git a/tests/Tools/ReportTest.php b/tests/Tools/ReportTest.php new file mode 100644 index 0000000..208bd7b --- /dev/null +++ b/tests/Tools/ReportTest.php @@ -0,0 +1,109 @@ +httpClientMock = $this->getMockBuilder('SubscribePro\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->reportTool = $this->getMockBuilder('SubscribePro\Tools\Report') + ->setMethods(['isResource', 'isWritable']) + ->setConstructorArgs([$this->httpClientMock]) + ->getMock(); + } + + /** + * @expectedException \SubscribePro\Exception\InvalidArgumentException + * @expectedExceptionMessageRegExp /Invalid report code. Allowed values: [a-z,_ ]+/ + */ + public function testFailToLoadReportIfReportCodeIsNotValid() + { + $this->httpClientMock->expects($this->never()) + ->method('getToSink'); + + $this->reportTool->loadReport('some_invalid_code', 'filePath'); + } + + /** + * @expectedException \SubscribePro\Exception\InvalidArgumentException + * @expectedExceptionMessage file/path is not writable or a directory. + */ + public function testFailToLoadReportIfUnableToWriteToFile() + { + $filePath = 'file/path'; + $code = Report::REPORT_CUSTOMER_ACTIVITY; + + $this->reportTool->expects($this->once()) + ->method('isResource') + ->with($filePath) + ->willReturn(false); + + $this->reportTool->expects($this->once()) + ->method('isWritable') + ->with($filePath) + ->willReturn(false); + + $this->httpClientMock->expects($this->never()) + ->method('getToSink'); + + $this->reportTool->loadReport($code, $filePath); + } + + public function testLoadReportIfFileIsResource() + { + $filePath = 'file/path'; + $code = Report::REPORT_CUSTOMER_ACTIVITY; + + $this->reportTool->expects($this->once()) + ->method('isResource') + ->with($filePath) + ->willReturn(true); + + $this->reportTool->expects($this->never()) + ->method('isWritable'); + + $this->httpClientMock->expects($this->once()) + ->method('getToSink') + ->with("/services/v2/reports/{$code}", $filePath); + + $this->reportTool->loadReport($code, $filePath); + } + + public function testLoadReportIfFileIsWritable() + { + $filePath = 'file/path'; + $code = Report::REPORT_CUSTOMER_ACTIVITY; + + $this->reportTool->expects($this->once()) + ->method('isResource') + ->with($filePath) + ->willReturn(false); + + $this->reportTool->expects($this->once()) + ->method('isWritable') + ->with($filePath) + ->willReturn(true); + + $this->httpClientMock->expects($this->once()) + ->method('getToSink') + ->with("/services/v2/reports/{$code}", $filePath); + + $this->reportTool->loadReport($code, $filePath); + } +}