From 03466cf0ad2b2422f9df7317657f1ff2815ca7c3 Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Thu, 30 Apr 2015 17:35:57 +0300 Subject: [PATCH 1/6] Curl retry, curl timeouts & verify options --- lib/RetailCrm/Http/Client.php | 34 +++++++++++++++---- .../Tests/ApiClientReferenceTest.php | 1 + tests/RetailCrm/Tests/Http/ClientTest.php | 6 ++-- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/RetailCrm/Http/Client.php b/lib/RetailCrm/Http/Client.php index 5dbc826..da0c074 100644 --- a/lib/RetailCrm/Http/Client.php +++ b/lib/RetailCrm/Http/Client.php @@ -15,6 +15,7 @@ class Client protected $url; protected $defaultParameters; + protected $retry; public function __construct($url, array $defaultParameters = array()) { @@ -24,6 +25,7 @@ class Client $this->url = $url; $this->defaultParameters = $defaultParameters; + $this->retry = 0; } /** @@ -33,9 +35,10 @@ class Client * @param string $method (default: 'GET') * @param array $parameters (default: array()) * @param int $timeout + * @param bool $verify * @return ApiResponse */ - public function makeRequest($path, $method, array $parameters = array(), $timeout = 30) + public function makeRequest($path, $method, array $parameters = array(), $timeout = 30, $verify=false) { $allowedMethods = array(self::METHOD_GET, self::METHOD_POST); if (!in_array($method, $allowedMethods)) { @@ -55,20 +58,21 @@ class Client $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $path); + curl_setopt($ch, CURLOPT_TIMEOUT, (int)$timeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_FAILONERROR, false); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return into a variable - curl_setopt($ch, CURLOPT_TIMEOUT, (int) $timeout); // times out after 30s - // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // allow redirects + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verify); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verify); if (self::METHOD_POST === $method) { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); } - $responseBody = curl_exec($ch); + $responseBody = $this->curlExec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $errno = curl_errno($ch); $error = curl_error($ch); curl_close($ch); @@ -79,4 +83,20 @@ class Client return new ApiResponse($statusCode, $responseBody); } + + + /** + * @param resource $ch + * @return mixed + */ + private function curlExec($ch) { + $exec = curl_exec($ch); + + if (curl_errno($ch) && in_array(curl_errno($ch), array(6, 7, 28, 34, 35)) && $this->retry < 3) { + $this->retry += 1; + $this->curlExec($ch); + } + + return $exec; + } } diff --git a/tests/RetailCrm/Tests/ApiClientReferenceTest.php b/tests/RetailCrm/Tests/ApiClientReferenceTest.php index 1f10988..b31415e 100644 --- a/tests/RetailCrm/Tests/ApiClientReferenceTest.php +++ b/tests/RetailCrm/Tests/ApiClientReferenceTest.php @@ -9,6 +9,7 @@ class ApiClientReferenceTest extends TestCase /** * @group integration * @dataProvider getListDictionaries + * @param $name */ public function testList($name) { diff --git a/tests/RetailCrm/Tests/Http/ClientTest.php b/tests/RetailCrm/Tests/Http/ClientTest.php index 6e46653..62a1150 100644 --- a/tests/RetailCrm/Tests/Http/ClientTest.php +++ b/tests/RetailCrm/Tests/Http/ClientTest.php @@ -24,7 +24,7 @@ class ClientTest extends TestCase */ public function testHttpRequiring() { - $client = new Client('http://a.intarocrm.ru', array('apiKey' => '123')); + $client = new Client('http://demo.intarocrm.ru/api/' . ApiClient::VERSION, array('apiKey' => '123')); } /** @@ -33,13 +33,13 @@ class ClientTest extends TestCase */ public function testMakeRequestWrongMethod() { - $client = new Client('https://asdf.df', array()); + $client = new Client('https://demo.intarocrm.ru/api/' . ApiClient::VERSION, array('apiKey' => '123')); $client->makeRequest('/a', 'adsf'); } /** * @group integration - * @expectedException RetailCrm\Exception\CurlException + * @expectedException \RetailCrm\Exception\CurlException */ public function testMakeRequestWrongUrl() { From c7e7ed92cf0ca336971ce300684b0d3be672353a Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Mon, 4 May 2015 18:47:14 +0300 Subject: [PATCH 2/6] packs & inventories, improve curl connection timeout handler --- lib/RetailCrm/ApiClient.php | 68 ++++++++++++++ lib/RetailCrm/Http/Client.php | 14 ++- tests/RetailCrm/Test/TestCase.php | 19 +++- tests/RetailCrm/Tests/ApiClientOrdersTest.php | 21 +++++ tests/RetailCrm/Tests/ApiClientStoreTest.php | 92 +++++++++++++++++++ tests/RetailCrm/Tests/Http/ClientTest.php | 18 +++- 6 files changed, 223 insertions(+), 9 deletions(-) create mode 100644 tests/RetailCrm/Tests/ApiClientStoreTest.php diff --git a/lib/RetailCrm/ApiClient.php b/lib/RetailCrm/ApiClient.php index e77740a..8f63fc9 100644 --- a/lib/RetailCrm/ApiClient.php +++ b/lib/RetailCrm/ApiClient.php @@ -221,6 +221,31 @@ class ApiClient 'orders' => json_encode($ids), )); } + + /** + * Get orders assembly history + * + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * @return ApiResponse + */ + public function ordersPacksHistory(array $filter = array(), $page = null, $limit = null) + { + $parameters = array(); + + if (sizeof($filter)) { + $parameters['filter'] = $filter; + } + if (null !== $page) { + $parameters['page'] = (int) $page; + } + if (null !== $limit) { + $parameters['limit'] = (int) $limit; + } + + return $this->client->makeRequest('/orders/packs/history', Client::METHOD_GET, $parameters); + } /** * Create a customer @@ -347,6 +372,49 @@ class ApiClient )); } + /** + * Get purchace prices & stock balance + * + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * @return ApiResponse + */ + public function storeInventories(array $filter = array(), $page = null, $limit = null) + { + $parameters = array(); + + if (sizeof($filter)) { + $parameters['filter'] = $filter; + } + if (null !== $page) { + $parameters['page'] = (int) $page; + } + if (null !== $limit) { + $parameters['limit'] = (int) $limit; + } + + return $this->client->makeRequest('/store/inventories', Client::METHOD_GET, $parameters); + } + + /** + * Upload store inventories + * + * @param array $offers + * @param string $site (default: null) + * @return ApiResponse + */ + public function storeInventoriesUpload(array $offers, $site = null) + { + if (!sizeof($offers)) { + throw new \InvalidArgumentException('Parameter `offers` must contains array of the customers'); + } + + return $this->client->makeRequest("/store/inventories/upload", Client::METHOD_POST, $this->fillSite($site, array( + 'offers' => json_encode($offers), + ))); + } + /** * Returns deliveryServices list * diff --git a/lib/RetailCrm/Http/Client.php b/lib/RetailCrm/Http/Client.php index da0c074..b57e170 100644 --- a/lib/RetailCrm/Http/Client.php +++ b/lib/RetailCrm/Http/Client.php @@ -38,7 +38,7 @@ class Client * @param bool $verify * @return ApiResponse */ - public function makeRequest($path, $method, array $parameters = array(), $timeout = 30, $verify=false) + public function makeRequest($path, $method, array $parameters = array(), $timeout = 30, $verify = false, $debug = false) { $allowedMethods = array(self::METHOD_GET, self::METHOD_POST); if (!in_array($method, $allowedMethods)) { @@ -71,7 +71,7 @@ class Client curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); } - $responseBody = $this->curlExec($ch); + $responseBody = $this->curlExec($ch, $debug); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $errno = curl_errno($ch); $error = curl_error($ch); @@ -87,14 +87,20 @@ class Client /** * @param resource $ch + * @param boolean $debug * @return mixed */ - private function curlExec($ch) { + private function curlExec($ch, $debug) { $exec = curl_exec($ch); if (curl_errno($ch) && in_array(curl_errno($ch), array(6, 7, 28, 34, 35)) && $this->retry < 3) { $this->retry += 1; - $this->curlExec($ch); + + if ($debug) { + error_log('CURL RETRY #' . $this->retry . PHP_EOL, 4); + } + + $this->curlExec($ch, $debug); } return $exec; diff --git a/tests/RetailCrm/Test/TestCase.php b/tests/RetailCrm/Test/TestCase.php index cb58cce..5eafabb 100644 --- a/tests/RetailCrm/Test/TestCase.php +++ b/tests/RetailCrm/Test/TestCase.php @@ -3,6 +3,7 @@ namespace RetailCrm\Test; use RetailCrm\ApiClient; +use RetailCrm\Http\Client; class TestCase extends \PHPUnit_Framework_TestCase { @@ -22,4 +23,20 @@ class TestCase extends \PHPUnit_Framework_TestCase $site ?: (isset($_SERVER['CRM_SITE']) ? $_SERVER['CRM_SITE'] : null) ); } -} \ No newline at end of file + + /** + * Return Client object + * + * @param string $url (default: null) + * @param string $apiKey (default: null) + * @return Client + */ + public static function getClient($url = null, $defaultParameters = array()) + { + return new Client( + $url ?: $_SERVER['CRM_URL'] . '/api/' . ApiClient::VERSION, + array('apiKey' => isset($defaultParameters['apiKey']) ? $defaultParameters['apiKey'] : $_SERVER['CRM_API_KEY']) + ); + } +} + diff --git a/tests/RetailCrm/Tests/ApiClientOrdersTest.php b/tests/RetailCrm/Tests/ApiClientOrdersTest.php index 8d1254d..1c1fc32 100644 --- a/tests/RetailCrm/Tests/ApiClientOrdersTest.php +++ b/tests/RetailCrm/Tests/ApiClientOrdersTest.php @@ -199,6 +199,27 @@ class ApiClientOrdersTest extends TestCase 'API returns generatedAt in orders history' ); } + + /** + * @group integration + */ + public function testOrdersPacksHistory() + { + $client = static::getApiClient(); + + $response = $client->ordersPacksHistory(); + $this->assertInstanceOf('RetailCrm\Response\ApiResponse', $response); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue($response->success); + $this->assertTrue( + isset($response['history']), + 'API returns orders assembly history' + ); + $this->assertTrue( + isset($response['generatedAt']), + 'API returns generatedAt in orders assembly history' + ); + } /** * @group integration diff --git a/tests/RetailCrm/Tests/ApiClientStoreTest.php b/tests/RetailCrm/Tests/ApiClientStoreTest.php new file mode 100644 index 0000000..de354b0 --- /dev/null +++ b/tests/RetailCrm/Tests/ApiClientStoreTest.php @@ -0,0 +1,92 @@ +storeInventories(); + $this->assertInstanceOf('RetailCrm\Response\ApiResponse', $response); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue($response->success); + $this->assertTrue( + isset($response['offers']), + 'API returns orders assembly history' + ); + } + + /** + * @group unit + * @expectedException \InvalidArgumentException + */ + public function testStoreInventoriesUploadExceptionEmpty() + { + $client = static::getApiClient(); + + $response = $client->storeInventoriesUpload(array()); + } + + /** + * @group integration + */ + public function testStoreInventoriesUpload() + { + $client = static::getApiClient(); + + $externalIdA = 'upload-a-' . time(); + $externalIdB = 'upload-b-' . time(); + + $response = $client->storeInventoriesUpload(array( + array( + 'externalId' => $externalIdA, + 'available' => 10, + 'purchasePrice' => 1700 + ), + array( + 'externalId' => $externalIdB, + 'available' => 20, + 'purchasePrice' => 1500 + ), + )); + $this->assertInstanceOf('RetailCrm\Response\ApiResponse', $response); + $this->assertTrue( + $response->isSuccessful(), + 'Got offer' + ); + } + + /** + * @group integration + */ + public function testStoreInventoriesUploadFailed() + { + $client = static::getApiClient(); + + $externalIdA = 'upload-a-' . time(); + $externalIdB = 'upload-b-' . time(); + + $response = $client->storeInventoriesUpload(array( + array( + 'externalId' => $externalIdA, + 'available' => 10, + 'purchasePrice' => 1700 + ), + array( + 'externalId' => $externalIdB, + 'available' => 20, + 'purchasePrice' => 1500 + ), + )); + $this->assertInstanceOf('RetailCrm\Response\ApiResponse', $response); + $this->assertEquals(400, $response->getStatusCode()); + $this->assertTrue(isset($response['errorMsg']), $response['errorMsg']); + } +} diff --git a/tests/RetailCrm/Tests/Http/ClientTest.php b/tests/RetailCrm/Tests/Http/ClientTest.php index 62a1150..84eecf2 100644 --- a/tests/RetailCrm/Tests/Http/ClientTest.php +++ b/tests/RetailCrm/Tests/Http/ClientTest.php @@ -24,7 +24,7 @@ class ClientTest extends TestCase */ public function testHttpRequiring() { - $client = new Client('http://demo.intarocrm.ru/api/' . ApiClient::VERSION, array('apiKey' => '123')); + $client = new Client('http://demo.retailcrm.ru/api/' . ApiClient::VERSION, array('apiKey' => '123')); } /** @@ -33,7 +33,7 @@ class ClientTest extends TestCase */ public function testMakeRequestWrongMethod() { - $client = new Client('https://demo.intarocrm.ru/api/' . ApiClient::VERSION, array('apiKey' => '123')); + $client = static::getClient(); $client->makeRequest('/a', 'adsf'); } @@ -52,10 +52,20 @@ class ClientTest extends TestCase */ public function testMakeRequestSuccess() { - $client = new Client('https://demo.intarocrm.ru/api/' . ApiClient::VERSION, array()); + $client = static::getClient(); $response = $client->makeRequest('/orders', Client::METHOD_GET); $this->assertInstanceOf('RetailCrm\Response\ApiResponse', $response); - $this->assertEquals(403, $response->getStatusCode()); + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @group integration + * @expectedException \RetailCrm\Exception\CurlException + */ + public function testMakeRequestTimeout() + { + $client = static::getClient(); + $client->makeRequest('/orders', Client::METHOD_GET, array(), 1, false, true); } } From d1b26b1663284eef73ee748750528337cacaef65 Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Mon, 4 May 2015 18:54:57 +0300 Subject: [PATCH 3/6] minor fixes --- lib/RetailCrm/Http/Client.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/RetailCrm/Http/Client.php b/lib/RetailCrm/Http/Client.php index b57e170..8b65f41 100644 --- a/lib/RetailCrm/Http/Client.php +++ b/lib/RetailCrm/Http/Client.php @@ -36,6 +36,7 @@ class Client * @param array $parameters (default: array()) * @param int $timeout * @param bool $verify + * @param bool $debug * @return ApiResponse */ public function makeRequest($path, $method, array $parameters = array(), $timeout = 30, $verify = false, $debug = false) From 7ec35c2ad8563c8c009d37ac1cfc0adff505e798 Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Mon, 4 May 2015 18:56:30 +0300 Subject: [PATCH 4/6] minor fixes --- lib/RetailCrm/Http/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/RetailCrm/Http/Client.php b/lib/RetailCrm/Http/Client.php index 8b65f41..b0e66d9 100644 --- a/lib/RetailCrm/Http/Client.php +++ b/lib/RetailCrm/Http/Client.php @@ -59,8 +59,8 @@ class Client $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $path); - curl_setopt($ch, CURLOPT_TIMEOUT, (int)$timeout); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); + curl_setopt($ch, CURLOPT_TIMEOUT, (int) $timeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $timeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_FAILONERROR, false); From de6dbdd87f55b4873c1c72aaef7c56775717f6f2 Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Fri, 8 May 2015 18:03:05 +0300 Subject: [PATCH 5/6] psr2 & rollback for timeout handle --- lib/RetailCrm/ApiClient.php | 32 ++++++++++++++---------- lib/RetailCrm/Http/Client.php | 46 ++++++++++++++++------------------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/lib/RetailCrm/ApiClient.php b/lib/RetailCrm/ApiClient.php index 8f63fc9..593c63f 100644 --- a/lib/RetailCrm/ApiClient.php +++ b/lib/RetailCrm/ApiClient.php @@ -221,7 +221,7 @@ class ApiClient 'orders' => json_encode($ids), )); } - + /** * Get orders assembly history * @@ -288,11 +288,14 @@ class ApiClient return $this->client->makeRequest( "/customers/" . $customer[$by] . "/edit", Client::METHOD_POST, - $this->fillSite($site, array( - 'customer' => json_encode($customer), - 'by' => $by, + $this->fillSite( + $site, + array( + 'customer' => json_encode($customer), + 'by' => $by + ) ) - )); + ); } /** @@ -375,12 +378,13 @@ class ApiClient /** * Get purchace prices & stock balance * - * @param array $filter (default: array()) - * @param int $page (default: null) - * @param int $limit (default: null) + * @param array $filter (default: array()) + * @param int $page (default: null) + * @param int $limit (default: null) + * @param string $site (default: null) * @return ApiResponse */ - public function storeInventories(array $filter = array(), $page = null, $limit = null) + public function storeInventories(array $filter = array(), $page = null, $limit = null, $site = null) { $parameters = array(); @@ -394,7 +398,7 @@ class ApiClient $parameters['limit'] = (int) $limit; } - return $this->client->makeRequest('/store/inventories', Client::METHOD_GET, $parameters); + return $this->client->makeRequest('/store/inventories', Client::METHOD_GET, $this->fillSite($site, $parameters)); } /** @@ -410,9 +414,11 @@ class ApiClient throw new \InvalidArgumentException('Parameter `offers` must contains array of the customers'); } - return $this->client->makeRequest("/store/inventories/upload", Client::METHOD_POST, $this->fillSite($site, array( - 'offers' => json_encode($offers), - ))); + return $this->client->makeRequest( + "/store/inventories/upload", + Client::METHOD_POST, + $this->fillSite($site, array('offers' => json_encode($offers))) + ); } /** diff --git a/lib/RetailCrm/Http/Client.php b/lib/RetailCrm/Http/Client.php index b0e66d9..30f3558 100644 --- a/lib/RetailCrm/Http/Client.php +++ b/lib/RetailCrm/Http/Client.php @@ -39,8 +39,14 @@ class Client * @param bool $debug * @return ApiResponse */ - public function makeRequest($path, $method, array $parameters = array(), $timeout = 30, $verify = false, $debug = false) - { + public function makeRequest( + $path, + $method, + array $parameters = array(), + $timeout = 30, + $verify = false, + $debug = false + ) { $allowedMethods = array(self::METHOD_GET, self::METHOD_POST); if (!in_array($method, $allowedMethods)) { throw new \InvalidArgumentException(sprintf( @@ -53,6 +59,7 @@ class Client $parameters = array_merge($this->defaultParameters, $parameters); $path = $this->url . $path; + if (self::METHOD_GET === $method && sizeof($parameters)) { $path .= '?' . http_build_query($parameters); } @@ -72,10 +79,21 @@ class Client curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters); } - $responseBody = $this->curlExec($ch, $debug); + $responseBody = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $errno = curl_errno($ch); $error = curl_error($ch); + + /*if ($errno && in_array($errno, array(6, 7, 28, 34, 35)) && $this->retry < 3) { + $this->retry += 1; + + if ($debug) { + error_log('CURL RETRY #' . $this->retry . PHP_EOL, 4); + } + + $this->makeRequest($path, $method, $parameters, $timeout, $verify, $debug); + }*/ + curl_close($ch); if ($errno) { @@ -84,26 +102,4 @@ class Client return new ApiResponse($statusCode, $responseBody); } - - - /** - * @param resource $ch - * @param boolean $debug - * @return mixed - */ - private function curlExec($ch, $debug) { - $exec = curl_exec($ch); - - if (curl_errno($ch) && in_array(curl_errno($ch), array(6, 7, 28, 34, 35)) && $this->retry < 3) { - $this->retry += 1; - - if ($debug) { - error_log('CURL RETRY #' . $this->retry . PHP_EOL, 4); - } - - $this->curlExec($ch, $debug); - } - - return $exec; - } } From 67e0f4746e1ac99b75c244a4debd4156f6ccdf5c Mon Sep 17 00:00:00 2001 From: Alex Lushpai Date: Wed, 20 May 2015 11:31:07 +0300 Subject: [PATCH 6/6] Update Client.php --- lib/RetailCrm/Http/Client.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/RetailCrm/Http/Client.php b/lib/RetailCrm/Http/Client.php index 30f3558..a3374dc 100644 --- a/lib/RetailCrm/Http/Client.php +++ b/lib/RetailCrm/Http/Client.php @@ -84,16 +84,6 @@ class Client $errno = curl_errno($ch); $error = curl_error($ch); - /*if ($errno && in_array($errno, array(6, 7, 28, 34, 35)) && $this->retry < 3) { - $this->retry += 1; - - if ($debug) { - error_log('CURL RETRY #' . $this->retry . PHP_EOL, 4); - } - - $this->makeRequest($path, $method, $parameters, $timeout, $verify, $debug); - }*/ - curl_close($ch); if ($errno) {