diff options
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | lib/SparkPost/SparkPost.php | 41 | ||||
-rw-r--r-- | test/unit/SparkPostTest.php | 76 |
3 files changed, 118 insertions, 4 deletions
@@ -102,6 +102,11 @@ $sparky = new SparkPost($httpClient, ['key'=>'YOUR_API_KEY']); * Type: `Boolean` * Default: `true` * `async` defines if the `request` function sends an asynchronous or synchronous request. If your client does not support async requests set this to `false` +* `options.retries` + * Required: No + * Type: `Number` + * Default: `0` + * `retries` controls how many API call attempts the client makes after receiving a 5xx response * `options.debug` * Required: No * Type: `Boolean` diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 05be7f4..b3a9e36 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -41,6 +41,7 @@ class SparkPost 'version' => 'v1', 'async' => true, 'debug' => false, + 'retries' => 0 ]; /** @@ -97,13 +98,29 @@ class SparkPost $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); + $retries = $this->options['retries']; try { - return new SparkPostResponse($this->httpClient->sendRequest($request), $this->ifDebug($requestValues)); + if ($retries > 0) { + $resp = $this->syncReqWithRetry($request, $retries); + } else { + $resp = $this->httpClient->sendRequest($request); + } + return new SparkPostResponse($resp, $this->ifDebug($requestValues)); } catch (\Exception $exception) { throw new SparkPostException($exception, $this->ifDebug($requestValues)); } } + private function syncReqWithRetry($request, $retries) + { + $resp = $this->httpClient->sendRequest($request); + $status = $resp->getStatusCode(); + if ($status >= 500 && $status <= 599 && $retries > 0) { + return $this->syncReqWithRetry($request, $retries-1); + } + return $resp; + } + /** * Sends async request to SparkPost API. * @@ -120,12 +137,28 @@ class SparkPost $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); - return new SparkPostPromise($this->httpClient->sendAsyncRequest($request), $this->ifDebug($requestValues)); + $retries = $this->options['retries']; + if ($retries > 0) { + return new SparkPostPromise($this->asyncReqWithRetry($request, $retries), $this->ifDebug($requestValues)); + } else { + return new SparkPostPromise($this->httpClient->sendAsyncRequest($request), $this->ifDebug($requestValues)); + } } else { throw new \Exception('Your http client does not support asynchronous requests. Please use a different client or use synchronous requests.'); } } + private function asyncReqWithRetry($request, $retries) + { + return $this->httpClient->sendAsyncRequest($request)->then(function($response) use ($request, $retries) { + $status = $response->getStatusCode(); + if ($status >= 500 && $status <= 599 && $retries > 0) { + return $this->asyncReqWithRetry($request, $retries-1); + } + return $response; + }); + } + /** * Builds request values from given params. * @@ -252,7 +285,7 @@ class SparkPost } $this->httpClient = $httpClient; - + return $this; } @@ -283,7 +316,7 @@ class SparkPost $this->options[$option] = $value; } } - + return $this; } diff --git a/test/unit/SparkPostTest.php b/test/unit/SparkPostTest.php index cb139f6..89e8e3a 100644 --- a/test/unit/SparkPostTest.php +++ b/test/unit/SparkPostTest.php @@ -60,6 +60,13 @@ class SparkPostTest extends \PHPUnit_Framework_TestCase $this->responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->responseBody)); + $errorBodyMock = Mockery::mock(); + $this->badResponseBody = ['errors' => []]; + $this->badResponseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); + $this->badResponseMock->shouldReceive('getStatusCode')->andReturn(503); + $this->badResponseMock->shouldReceive('getBody')->andReturn($errorBodyMock); + $errorBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->badResponseBody)); + // exception mock up $exceptionResponseMock = Mockery::mock(); $this->exceptionBody = ['results' => 'failed']; @@ -159,6 +166,35 @@ class SparkPostTest extends \PHPUnit_Framework_TestCase } } + public function testSuccessfulSyncRequestWithRetries() + { + $this->clientMock->shouldReceive('sendRequest')-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andReturn($this->badResponseMock, $this->badResponseMock, $this->responseMock); + + $this->resource->setOptions(['retries' => 2]); + $response = $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); + + $this->assertEquals($this->responseBody, $response->getBody()); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testUnsuccessfulSyncRequestWithRetries() + { + $this->clientMock->shouldReceive('sendRequest')-> + once()-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andThrow($this->exceptionMock); + + $this->resource->setOptions(['retries' => 2]); + try { + $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); + } catch (\Exception $e) { + $this->assertEquals($this->exceptionBody, $e->getBody()); + $this->assertEquals(500, $e->getCode()); + } + } + public function testSuccessfulAsyncRequestWithWait() { $this->promiseMock->shouldReceive('wait')->andReturn($this->responseMock); @@ -212,6 +248,46 @@ class SparkPostTest extends \PHPUnit_Framework_TestCase })->wait(); } + public function testSuccessfulAsyncRequestWithRetries() + { + $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); + $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $clientMock->shouldReceive('sendAsyncRequest')-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andReturn( + new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), + new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), + new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->responseMock), $testReq) + ); + + $resource = new SparkPost($clientMock, ['key' => 'SPARKPOST_API_KEY']); + + $resource->setOptions(['async' => true, 'retries' => 2]); + $promise = $resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); + $promise->then(function($resp) { + $this->assertEquals(200, $resp->getStatusCode()); + })->wait(); + } + + public function testUnsuccessfulAsyncRequestWithRetries() + { + $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); + $rejectedPromise = new GuzzleRejectedPromise($this->exceptionMock); + $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $clientMock->shouldReceive('sendAsyncRequest')-> + with(Mockery::type('GuzzleHttp\Psr7\Request'))-> + andReturn(new GuzzleAdapterPromise($rejectedPromise, $testReq)); + + $resource = new SparkPost($clientMock, ['key' => 'SPARKPOST_API_KEY']); + + $resource->setOptions(['async' => true, 'retries' => 2]); + $promise = $resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); + $promise->then(null, function($exception) { + $this->assertEquals(500, $exception->getCode()); + $this->assertEquals($this->exceptionBody, $exception->getBody()); + })->wait(); + } + public function testPromise() { $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); |