From 9b320a1d2e704acf505cb34dbc5485b6de0623f5 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Thu, 12 Feb 2026 15:50:58 +0000 Subject: [PATCH 01/12] updated return types and new decoded method --- lib/SparkPost/SparkPostResponse.php | 47 +++++++++++++++++------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/SparkPost/SparkPostResponse.php b/lib/SparkPost/SparkPostResponse.php index 402a2b2..4164cf4 100644 --- a/lib/SparkPost/SparkPostResponse.php +++ b/lib/SparkPost/SparkPostResponse.php @@ -2,6 +2,7 @@ namespace SparkPost; +use Psr\Http\Message\MessageInterface as MessageInterface; use Psr\Http\Message\ResponseInterface as ResponseInterface; use Psr\Http\Message\StreamInterface as StreamInterface; @@ -39,84 +40,90 @@ public function getRequest() } /** - * Returns the body. - * - * @return array $body - the json decoded body from the http response + * Returns the body as StreamInterface (PSR-7). */ - public function getBody() + public function getBody(): StreamInterface { - $body = $this->response->getBody(); - $body_string = $body->__toString(); + return $this->response->getBody(); + } + /** + * Returns the json decoded body from the http response. + * + * @return array $body + */ + public function getBodyDecoded(): array + { + $body_string = $this->response->getBody()->__toString(); $json = json_decode($body_string, true); - return $json; + return $json ?? []; } /** * pass these down to the response given in the constructor. */ - public function getProtocolVersion() + public function getProtocolVersion(): string { return $this->response->getProtocolVersion(); } - public function withProtocolVersion($version) + public function withProtocolVersion(string $version): MessageInterface { return $this->response->withProtocolVersion($version); } - public function getHeaders() + public function getHeaders(): array { return $this->response->getHeaders(); } - public function hasHeader($name) + public function hasHeader(string $name): bool { return $this->response->hasHeader($name); } - public function getHeader($name) + public function getHeader(string $name): array { return $this->response->getHeader($name); } - public function getHeaderLine($name) + public function getHeaderLine(string $name): string { return $this->response->getHeaderLine($name); } - public function withHeader($name, $value) + public function withHeader(string $name, $value): MessageInterface { return $this->response->withHeader($name, $value); } - public function withAddedHeader($name, $value) + public function withAddedHeader(string $name, $value): MessageInterface { return $this->response->withAddedHeader($name, $value); } - public function withoutHeader($name) + public function withoutHeader(string $name): MessageInterface { return $this->response->withoutHeader($name); } - public function withBody(StreamInterface $body) + public function withBody(StreamInterface $body): MessageInterface { return $this->response->withBody($body); } - public function getStatusCode() + public function getStatusCode(): int { return $this->response->getStatusCode(); } - public function withStatus($code, $reasonPhrase = '') + public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface { return $this->response->withStatus($code, $reasonPhrase); } - public function getReasonPhrase() + public function getReasonPhrase(): string { return $this->response->getReasonPhrase(); } From 49e44d1553c908a0ade6afed2b58f6252b457b47 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Thu, 12 Feb 2026 15:53:40 +0000 Subject: [PATCH 02/12] added readme note --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b68d450..b982796 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ # SparkPost PHP Library +## This fork is maintained internally due to inactivity in the upstream repository. Only used within Lightfoot projects. + [![Travis CI](https://travis-ci.org/SparkPost/php-sparkpost.svg?branch=master)](https://travis-ci.org/SparkPost/php-sparkpost) [![Coverage Status](https://coveralls.io/repos/SparkPost/php-sparkpost/badge.svg?branch=master&service=github)](https://coveralls.io/github/SparkPost/php-sparkpost?branch=master) [![Downloads](https://img.shields.io/packagist/dt/sparkpost/sparkpost.svg?maxAge=3600)](https://packagist.org/packages/sparkpost/sparkpost) From 75707e15456973cb3c35f486c2a2c1a79fb16493 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Thu, 12 Feb 2026 17:04:28 +0000 Subject: [PATCH 03/12] return types --- composer.json | 12 +- examples/bootstrap.php | 2 +- examples/debug/index.php | 10 +- .../message-events/get_message_events.php | 10 +- .../get_message_events_with_retry_logic.php | 10 +- examples/templates/create_template.php | 36 ++-- examples/templates/delete_template.php | 10 +- examples/templates/get_all_templates.php | 10 +- examples/templates/get_template.php | 10 +- examples/templates/preview_template.php | 10 +- examples/templates/update_template.php | 10 +- .../transmissions/create_transmission.php | 10 +- .../create_transmission_with_attachment.php | 16 +- .../create_transmission_with_cc_and_bcc.php | 10 +- ...reate_transmission_with_recipient_list.php | 10 +- .../create_transmission_with_template.php | 10 +- .../transmissions/delete_transmission.php | 10 +- lib/SparkPost/ResourceBase.php | 21 ++- lib/SparkPost/SparkPost.php | 155 ++++++++++------- lib/SparkPost/SparkPostException.php | 9 +- lib/SparkPost/SparkPostPromise.php | 24 +-- lib/SparkPost/SparkPostResponse.php | 7 +- lib/SparkPost/Transmission.php | 51 +++--- rector.php | 26 +++ test/unit/SparkPostResponseTest.php | 74 ++++---- test/unit/SparkPostTest.php | 163 ++++++++++-------- test/unit/TransmissionTest.php | 89 +++++----- 27 files changed, 460 insertions(+), 355 deletions(-) create mode 100644 rector.php diff --git a/composer.json b/composer.json index 1736421..a3d9b41 100644 --- a/composer.json +++ b/composer.json @@ -8,19 +8,27 @@ } ], "minimum-stability": "stable", + "config": { + "allow-plugins": { + "php-http/discovery": true + } + }, "scripts": { "test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit", "fix-style": "php-cs-fixer fix ." }, "require": { - "php": "^7.1 || ^8.0", + "php": "^7.4 || ^8.0", "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", "php-http/message": "^1.0", "php-http/client-implementation": "^1.0", - "php-http/discovery": "^1.0" + "php-http/discovery": "^1.0", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^8.0 || ^9.0", + "rector/rector": "^1.0", "php-http/guzzle6-adapter": "^1.0", "mockery/mockery": "^1.3", "nyholm/nsa": "^1.0", diff --git a/examples/bootstrap.php b/examples/bootstrap.php index 688d53e..6c8c4f5 100644 --- a/examples/bootstrap.php +++ b/examples/bootstrap.php @@ -1,3 +1,3 @@ getRequest()); echo "Response:\n"; - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { echo "Request:\n"; print_r($e->getRequest()); echo "Exception:\n"; - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/message-events/get_message_events.php b/examples/message-events/get_message_events.php index 92bc70b..40ca911 100644 --- a/examples/message-events/get_message_events.php +++ b/examples/message-events/get_message_events.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -20,9 +20,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/message-events/get_message_events_with_retry_logic.php b/examples/message-events/get_message_events_with_retry_logic.php index 82efd2a..a4d7ee2 100644 --- a/examples/message-events/get_message_events_with_retry_logic.php +++ b/examples/message-events/get_message_events_with_retry_logic.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -23,11 +23,11 @@ */ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; if ($e->getCode() >= 500 && $e->getCode() <= 599) { echo "Wow, this failed epically"; diff --git a/examples/templates/create_template.php b/examples/templates/create_template.php index 1d697d5..0ce0d0b 100644 --- a/examples/templates/create_template.php +++ b/examples/templates/create_template.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -22,16 +22,16 @@ // Valid short template content examples $plain_text = 'Write your text message part here.'; -$html = <<

Write your HTML message part here

-HTML; +HTML_WRAP; -$amp_html = << @@ -43,25 +43,25 @@ Hello World! Let's get started using AMP HTML together! -HTML; +HTML_WRAP; $promise = $sparky->request('POST', 'templates', [ - 'name' => $template_name, - 'id' => $template_id, - 'content' => [ - 'from' => "from@$sending_domain", - 'subject' => 'Your Subject', - 'text' => $plain_text, - 'html' => $html, - 'amp_html' => $amp_html, - ], + 'name' => $template_name, + 'id' => $template_id, + 'content' => [ + 'from' => "from@$sending_domain", + 'subject' => 'Your Subject', + 'text' => $plain_text, + 'html' => $html, + 'amp_html' => $amp_html, + ], ]); try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/templates/delete_template.php b/examples/templates/delete_template.php index 609e67e..34e66e7 100644 --- a/examples/templates/delete_template.php +++ b/examples/templates/delete_template.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -19,9 +19,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/templates/get_all_templates.php b/examples/templates/get_all_templates.php index 87a5b4d..722126d 100644 --- a/examples/templates/get_all_templates.php +++ b/examples/templates/get_all_templates.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -17,9 +17,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/templates/get_template.php b/examples/templates/get_template.php index 3dd8849..647e022 100644 --- a/examples/templates/get_template.php +++ b/examples/templates/get_template.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -19,9 +19,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/templates/preview_template.php b/examples/templates/preview_template.php index 51675a2..8af61d9 100644 --- a/examples/templates/preview_template.php +++ b/examples/templates/preview_template.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -23,9 +23,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/templates/update_template.php b/examples/templates/update_template.php index 2f79cfd..83a1c05 100644 --- a/examples/templates/update_template.php +++ b/examples/templates/update_template.php @@ -2,7 +2,7 @@ namespace Examples\Templates; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -23,9 +23,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/transmissions/create_transmission.php b/examples/transmissions/create_transmission.php index 6204ff8..a137f9e 100644 --- a/examples/transmissions/create_transmission.php +++ b/examples/transmissions/create_transmission.php @@ -2,7 +2,7 @@ namespace Examples\Transmissions; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -40,9 +40,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/transmissions/create_transmission_with_attachment.php b/examples/transmissions/create_transmission_with_attachment.php index b97dd1d..2d85e5d 100644 --- a/examples/transmissions/create_transmission_with_attachment.php +++ b/examples/transmissions/create_transmission_with_attachment.php @@ -2,7 +2,7 @@ namespace Examples\Transmissions; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -13,10 +13,10 @@ // In these examples, fetch API key from environment variable $sparky = new SparkPost($httpClient, ["key" => getenv('SPARKPOST_API_KEY')]); -$filePath = dirname(__FILE__).'/'; +$filePath = __DIR__ . '/'; $fileName = 'sparkpost.png'; -$fileType = mime_content_type($filePath.$fileName); -$fileData = base64_encode(file_get_contents($filePath.$fileName)); +$fileType = mime_content_type($filePath . $fileName); +$fileData = base64_encode(file_get_contents($filePath . $fileName)); // put your own sending domain and test recipient address here $sending_domain = "steve2-test.trymsys.net"; @@ -52,9 +52,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/transmissions/create_transmission_with_cc_and_bcc.php b/examples/transmissions/create_transmission_with_cc_and_bcc.php index c49ab0c..d7bdb11 100644 --- a/examples/transmissions/create_transmission_with_cc_and_bcc.php +++ b/examples/transmissions/create_transmission_with_cc_and_bcc.php @@ -2,7 +2,7 @@ namespace Examples\Transmissions; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -58,9 +58,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/transmissions/create_transmission_with_recipient_list.php b/examples/transmissions/create_transmission_with_recipient_list.php index b95a994..5b8eec3 100644 --- a/examples/transmissions/create_transmission_with_recipient_list.php +++ b/examples/transmissions/create_transmission_with_recipient_list.php @@ -2,7 +2,7 @@ namespace Examples\Transmissions; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -35,9 +35,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/transmissions/create_transmission_with_template.php b/examples/transmissions/create_transmission_with_template.php index 66de0b9..60a8d5a 100644 --- a/examples/transmissions/create_transmission_with_template.php +++ b/examples/transmissions/create_transmission_with_template.php @@ -2,7 +2,7 @@ namespace Examples\Transmissions; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -34,9 +34,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/examples/transmissions/delete_transmission.php b/examples/transmissions/delete_transmission.php index e7fb65a..da51da9 100644 --- a/examples/transmissions/delete_transmission.php +++ b/examples/transmissions/delete_transmission.php @@ -2,7 +2,7 @@ namespace Examples\Transmissions; -require dirname(__FILE__).'/../bootstrap.php'; +require __DIR__ . '/../bootstrap.php'; use SparkPost\SparkPost; use GuzzleHttp\Client; @@ -20,9 +20,9 @@ try { $response = $promise->wait(); - echo $response->getStatusCode()."\n"; - print_r($response->getBody())."\n"; + echo $response->getStatusCode() . "\n"; + print_r($response->getBody()) . "\n"; } catch (\Exception $e) { - echo $e->getCode()."\n"; - echo $e->getMessage()."\n"; + echo $e->getCode() . "\n"; + echo $e->getMessage() . "\n"; } diff --git a/lib/SparkPost/ResourceBase.php b/lib/SparkPost/ResourceBase.php index 3620192..9bd5517 100644 --- a/lib/SparkPost/ResourceBase.php +++ b/lib/SparkPost/ResourceBase.php @@ -10,20 +10,20 @@ class ResourceBase /** * SparkPost object used to make requests. */ - protected $sparkpost; + protected SparkPost $sparkpost; /** * The api endpoint that gets prepended to all requests send through this resource. */ - protected $endpoint; + protected string $endpoint; /** * Sets up the Resource. * * @param SparkPost $sparkpost - the sparkpost instance that this resource is attached to - * @param string $endpoint - the endpoint that this resource wraps + * @param string $endpoint - the endpoint that this resource wraps */ - public function __construct(SparkPost $sparkpost, $endpoint) + public function __construct(SparkPost $sparkpost, string $endpoint) { $this->sparkpost = $sparkpost; $this->endpoint = $endpoint; @@ -32,6 +32,8 @@ public function __construct(SparkPost $sparkpost, $endpoint) /** * Sends get request to API at the set endpoint. * + * @return SparkPostPromise|SparkPostResponse + * @throws SparkPostException * @see SparkPost->request() */ public function get($uri = '', $payload = [], $headers = []) @@ -42,6 +44,8 @@ public function get($uri = '', $payload = [], $headers = []) /** * Sends put request to API at the set endpoint. * + * @return SparkPostPromise|SparkPostResponse + * @throws SparkPostException * @see SparkPost->request() */ public function put($uri = '', $payload = [], $headers = []) @@ -52,6 +56,8 @@ public function put($uri = '', $payload = [], $headers = []) /** * Sends post request to API at the set endpoint. * + * @return SparkPostPromise|SparkPostResponse + * @throws SparkPostException * @see SparkPost->request() */ public function post($payload = [], $headers = []) @@ -62,6 +68,8 @@ public function post($payload = [], $headers = []) /** * Sends delete request to API at the set endpoint. * + * @return SparkPostPromise|SparkPostResponse + * @throws SparkPostException * @see SparkPost->request() */ public function delete($uri = '', $payload = [], $headers = []) @@ -72,9 +80,10 @@ public function delete($uri = '', $payload = [], $headers = []) /** * Sends requests to SparkPost object to the resource endpoint. * + * @return SparkPostPromise|SparkPostResponse depending on sync or async request + * @throws SparkPostException * @see SparkPost->request() * - * @return SparkPostPromise or SparkPostResponse depending on sync or async request */ public function request($method = 'GET', $uri = '', $payload = [], $headers = []) { @@ -84,7 +93,7 @@ public function request($method = 'GET', $uri = '', $payload = [], $headers = [] $uri = ''; } - $uri = $this->endpoint.'/'.$uri; + $uri = $this->endpoint . '/' . $uri; return $this->sparkpost->request($method, $uri, $payload, $headers); } diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 140caf7..f346877 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -2,6 +2,7 @@ namespace SparkPost; +use Http\Client\Exception; use Http\Client\HttpClient; use Http\Client\HttpAsyncClient; use Http\Discovery\MessageFactoryDiscovery; @@ -13,7 +14,7 @@ class SparkPost /** * @var string Library version, used for setting User-Agent */ - private $version = '2.3.0'; + private string $version = '2.3.0'; /** * @var HttpClient|HttpAsyncClient used to make requests @@ -23,17 +24,17 @@ class SparkPost /** * @var RequestFactory */ - private $messageFactory; + private RequestFactory $messageFactory; /** * @var array Options for requests */ - private $options; + private array $options; /** * Default options for requests that can be overridden with the setOptions function. */ - private static $defaultOptions = [ + private static array $defaultOptions = [ 'host' => 'api.sparkpost.com', 'protocol' => 'https', 'port' => 443, @@ -47,15 +48,16 @@ class SparkPost /** * @var Transmission Instance of Transmission class */ - public $transmissions; + public Transmission $transmissions; /** * Sets up the SparkPost instance. * * @param HttpClient $httpClient - An httplug client or adapter - * @param array $options - An array to overide default options or a string to be used as an API key + * @param array $options - An array to overide default options or a string to be used as an API key + * @throws \Exception */ - public function __construct($httpClient, array $options) + public function __construct(HttpClient $httpClient, array $options) { $this->setOptions($options); $this->setHttpClient($httpClient); @@ -67,12 +69,14 @@ public function __construct($httpClient, array $options) * * @param string $method * @param string $uri - * @param array $payload - either used as the request body or url query params - * @param array $headers + * @param array $payload - either used as the request body or url query params + * @param array $headers * * @return SparkPostPromise|SparkPostResponse Promise or Response depending on sync or async request + * @throws SparkPostException + * @throws \Exception */ - public function request($method = 'GET', $uri = '', $payload = [], $headers = []) + public function request(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []) { if ($this->options['async'] === true) { return $this->asyncRequest($method, $uri, $payload, $headers); @@ -86,37 +90,40 @@ public function request($method = 'GET', $uri = '', $payload = [], $headers = [] * * @param string $method * @param string $uri - * @param array $payload - * @param array $headers + * @param array $payload + * @param array $headers * * @return SparkPostResponse * - * @throws SparkPostException + * @throws SparkPostException|Exception */ - public function syncRequest($method = 'GET', $uri = '', $payload = [], $headers = []) + public function syncRequest(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []): SparkPostResponse { $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); - $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); + $request = call_user_func_array([$this, 'buildRequestInstance'], $requestValues); $retries = $this->options['retries']; try { - if ($retries > 0) { - $resp = $this->syncReqWithRetry($request, $retries); - } else { - $resp = $this->httpClient->sendRequest($request); - } + $resp = $retries > 0 + ? $this->syncReqWithRetry($request, $retries) + : $this->httpClient->sendRequest( + $request + ); return new SparkPostResponse($resp, $this->ifDebug($requestValues)); } catch (\Exception $exception) { throw new SparkPostException($exception, $this->ifDebug($requestValues)); } } + /** + * @throws Exception + */ 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 $this->syncReqWithRetry($request, $retries - 1); } return $resp; } @@ -126,34 +133,45 @@ private function syncReqWithRetry($request, $retries) * * @param string $method * @param string $uri - * @param array $payload - * @param array $headers + * @param array $payload + * @param array $headers * * @return SparkPostPromise + * @throws \Exception */ - public function asyncRequest($method = 'GET', $uri = '', $payload = [], $headers = []) + public function asyncRequest(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []): SparkPostPromise { if ($this->httpClient instanceof HttpAsyncClient) { $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); - $request = call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); + $request = call_user_func_array([$this, 'buildRequestInstance'], $requestValues); $retries = $this->options['retries']; if ($retries > 0) { - return new SparkPostPromise($this->asyncReqWithRetry($request, $retries), $this->ifDebug($requestValues)); + return new SparkPostPromise( + $this->asyncReqWithRetry($request, $retries), $this->ifDebug($requestValues) + ); } else { - return new SparkPostPromise($this->httpClient->sendAsyncRequest($request), $this->ifDebug($requestValues)); + 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.'); + throw new \Exception( + 'Your http client does not support asynchronous requests. Please use a different client or use synchronous requests.' + ); } } + /** + * @throws \Exception + */ private function asyncReqWithRetry($request, $retries) { - return $this->httpClient->sendAsyncRequest($request)->then(function($response) use ($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 $this->asyncReqWithRetry($request, $retries - 1); } return $response; }); @@ -164,12 +182,12 @@ private function asyncReqWithRetry($request, $retries) * * @param string $method * @param string $uri - * @param array $payload - * @param array $headers + * @param array $payload + * @param array $headers * * @return array $requestValues */ - public function buildRequestValues($method, $uri, $payload, $headers) + public function buildRequestValues(string $method, string $uri, array $payload, array $headers): array { $method = trim(strtoupper($method)); @@ -197,11 +215,13 @@ public function buildRequestValues($method, $uri, $payload, $headers) /** * Build RequestInterface from given params. * - * @param array $requestValues - * + * @param $method + * @param $url + * @param $headers + * @param $body * @return RequestInterface */ - public function buildRequestInstance($method, $url, $headers, $body) + public function buildRequestInstance($method, $url, $headers, $body): RequestInterface { return $this->getMessageFactory()->createRequest($method, $url, $headers, $body); } @@ -209,14 +229,16 @@ public function buildRequestInstance($method, $url, $headers, $body) /** * Build RequestInterface from given params. * - * @param array $requestValues - * + * @param $method + * @param $uri + * @param $payload + * @param $headers * @return RequestInterface */ - public function buildRequest($method, $uri, $payload, $headers) + public function buildRequest($method, $uri, $payload, $headers): RequestInterface { $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); - return call_user_func_array(array($this, 'buildRequestInstance'), $requestValues); + return call_user_func_array([$this, 'buildRequestInstance'], $requestValues); } /** @@ -226,12 +248,12 @@ public function buildRequest($method, $uri, $payload, $headers) * * @return array $headers - headers for the request */ - public function getHttpHeaders($headers = []) + public function getHttpHeaders(array $headers = []): array { $constantHeaders = [ 'Authorization' => $this->options['key'], 'Content-Type' => 'application/json', - 'User-Agent' => 'php-sparkpost/'.$this->version, + 'User-Agent' => 'php-sparkpost/' . $this->version, ]; foreach ($constantHeaders as $key => $value) { @@ -244,12 +266,12 @@ public function getHttpHeaders($headers = []) /** * Builds the request url from the options and given params. * - * @param string $path - the path in the url to hit - * @param array $params - query parameters to be encoded into the url + * @param string $path - the path in the url to hit + * @param array $params - query parameters to be encoded into the url * * @return string $url - the url to send the desired request to */ - public function getUrl($path, $params = []) + public function getUrl(string $path, array $params = []): string { $options = $this->options; @@ -259,12 +281,12 @@ public function getUrl($path, $params = []) $value = implode(',', $value); } - array_push($paramsArray, $key.'='.$value); + $paramsArray[] = $key . '=' . $value; } $paramsString = implode('&', $paramsArray); - return $options['protocol'].'://'.$options['host'].($options['port'] ? ':'.$options['port'] : '').'/api/'.$options['version'].'/'.$path.($paramsString ? '?'.$paramsString : ''); + return $options['protocol'] . '://' . $options['host'] . ($options['port'] ? ':' . $options['port'] : '') . '/api/' . $options['version'] . '/' . $path . ($paramsString !== '' && $paramsString !== '0' ? '?' . $paramsString : ''); } /** @@ -274,10 +296,16 @@ public function getUrl($path, $params = []) * * @return SparkPost */ - public function setHttpClient($httpClient) + public function setHttpClient($httpClient): self { - if (!($httpClient instanceof HttpAsyncClient || $httpClient instanceof HttpClient)) { - throw new \LogicException(sprintf('Parameter to SparkPost::setHttpClient must be instance of "%s" or "%s"', HttpClient::class, HttpAsyncClient::class)); + if (!$httpClient instanceof HttpClient && !$httpClient instanceof HttpAsyncClient) { + throw new \LogicException( + sprintf( + 'Parameter to SparkPost::setHttpClient must be instance of "%s" or "%s"', + HttpClient::class, + HttpAsyncClient::class + ) + ); } $this->httpClient = $httpClient; @@ -288,27 +316,30 @@ public function setHttpClient($httpClient) /** * Sets the options from the param and defaults for the SparkPost object. * - * @param array $options - either an string API key or an array of options + * @param string|array $options - either an string API key or an array of options * * @return SparkPost + * @throws \Exception */ - public function setOptions($options) + public function setOptions($options): self { // if the options map is a string we should assume that its an api key if (is_string($options)) { $options = ['key' => $options]; } + if (!isset($this->options)) { + $this->options = self::$defaultOptions; + } + // Validate API key because its required if (!isset($this->options['key']) && (!isset($options['key']) || !preg_match('/\S/', $options['key']))) { throw new \Exception('You must provide an API key'); } - $this->options = isset($this->options) ? $this->options : self::$defaultOptions; - // set options, overriding defaults foreach ($options as $option => $value) { - if (key_exists($option, $this->options)) { + if (array_key_exists($option, $this->options)) { $this->options[$option] = $value; } } @@ -319,11 +350,11 @@ public function setOptions($options) /** * Returns the given value if debugging, an empty instance otherwise. * - * @param any $param + * @param array $param * - * @return any $param + * @return array|null $param */ - private function ifDebug($param) + private function ifDebug(array $param): ?array { return $this->options['debug'] ? $param : null; } @@ -331,7 +362,7 @@ private function ifDebug($param) /** * Sets up any endpoints to custom classes e.g. $this->transmissions. */ - private function setupEndpoints() + private function setupEndpoints(): void { $this->transmissions = new Transmission($this); } @@ -339,9 +370,9 @@ private function setupEndpoints() /** * @return RequestFactory */ - private function getMessageFactory() + private function getMessageFactory(): RequestFactory { - if (!$this->messageFactory) { + if (!isset($this->messageFactory)) { $this->messageFactory = MessageFactoryDiscovery::find(); } @@ -353,7 +384,7 @@ private function getMessageFactory() * * @return SparkPost */ - public function setMessageFactory(RequestFactory $messageFactory) + public function setMessageFactory(RequestFactory $messageFactory): self { $this->messageFactory = $messageFactory; diff --git a/lib/SparkPost/SparkPostException.php b/lib/SparkPost/SparkPostException.php index bde2e5e..4bf614b 100644 --- a/lib/SparkPost/SparkPostException.php +++ b/lib/SparkPost/SparkPostException.php @@ -14,12 +14,13 @@ class SparkPostException extends \Exception /** * Array with the request values sent. */ - private $request; + private ?array $request; /** * Sets up the custom exception and copies over original exception values. * - * @param Exception $exception - the exception to be wrapped + * @param \Exception $exception - the exception to be wrapped + * @param null $request */ public function __construct(\Exception $exception, $request = null) { @@ -41,7 +42,7 @@ public function __construct(\Exception $exception, $request = null) * * @return array $request */ - public function getRequest() + public function getRequest(): ?array { return $this->request; } @@ -51,7 +52,7 @@ public function getRequest() * * @return array $body - the json decoded body from the http response */ - public function getBody() + public function getBody(): ?array { return $this->body; } diff --git a/lib/SparkPost/SparkPostPromise.php b/lib/SparkPost/SparkPostPromise.php index b7ded0c..6c2d66b 100644 --- a/lib/SparkPost/SparkPostPromise.php +++ b/lib/SparkPost/SparkPostPromise.php @@ -9,17 +9,18 @@ class SparkPostPromise implements HttpPromise /** * HttpPromise to be wrapped by SparkPostPromise. */ - private $promise; + private HttpPromise $promise; /** * Array with the request values sent. */ - private $request; + private ?array $request; /** * set the promise to be wrapped. * * @param HttpPromise $promise + * @param null $request */ public function __construct(HttpPromise $promise, $request = null) { @@ -30,18 +31,19 @@ public function __construct(HttpPromise $promise, $request = null) /** * Hand off the response functions to the original promise and return a custom response or exception. * - * @param callable $onFulfilled - function to be called if the promise is fulfilled - * @param callable $onRejected - function to be called if the promise is rejected + * @param callable|null $onFulfilled - function to be called if the promise is fulfilled + * @param callable|null $onRejected - function to be called if the promise is rejected + * @return HttpPromise */ - public function then(callable $onFulfilled = null, callable $onRejected = null) + public function then(callable $onFulfilled = null, callable $onRejected = null): HttpPromise { $request = $this->request; - return $this->promise->then(function ($response) use ($onFulfilled, $request) { + return $this->promise->then(function ($response) use ($onFulfilled, $request): void { if (isset($onFulfilled)) { $onFulfilled(new SparkPostResponse($response, $request)); } - }, function ($exception) use ($onRejected, $request) { + }, function ($exception) use ($onRejected, $request): void { if (isset($onRejected)) { $onRejected(new SparkPostException($exception, $request)); } @@ -51,9 +53,9 @@ public function then(callable $onFulfilled = null, callable $onRejected = null) /** * Hand back the state. * - * @return $state - returns the state of the promise + * @return string $state - returns the state of the promise */ - public function getState() + public function getState(): string { return $this->promise->getState(); } @@ -67,13 +69,13 @@ public function getState() * * @throws SparkPostException */ - public function wait($unwrap = true) + public function wait($unwrap = true): SparkPostResponse { try { $response = $this->promise->wait($unwrap); return $response ? new SparkPostResponse($response, $this->request) : $response; - } catch (\Exception $exception) { + } catch (\Exception| \Throwable $exception) { throw new SparkPostException($exception, $this->request); } } diff --git a/lib/SparkPost/SparkPostResponse.php b/lib/SparkPost/SparkPostResponse.php index 4164cf4..a7ba1c3 100644 --- a/lib/SparkPost/SparkPostResponse.php +++ b/lib/SparkPost/SparkPostResponse.php @@ -11,17 +11,18 @@ class SparkPostResponse implements ResponseInterface /** * ResponseInterface to be wrapped by SparkPostResponse. */ - private $response; + private ResponseInterface $response; /** * Array with the request values sent. */ - private $request; + private ?array $request; /** * set the response to be wrapped. * * @param ResponseInterface $response + * @param null $request */ public function __construct(ResponseInterface $response, $request = null) { @@ -34,7 +35,7 @@ public function __construct(ResponseInterface $response, $request = null) * * @return array $request */ - public function getRequest() + public function getRequest(): ?array { return $this->request; } diff --git a/lib/SparkPost/Transmission.php b/lib/SparkPost/Transmission.php index 8efc934..c77e1eb 100644 --- a/lib/SparkPost/Transmission.php +++ b/lib/SparkPost/Transmission.php @@ -30,13 +30,13 @@ public function post($payload = [], $headers = []) * * @return array - the modified request body */ - public function formatPayload($payload) + public function formatPayload(array $payload): array { $payload = $this->formatBlindCarbonCopy($payload); //Fixes BCCs into payload $payload = $this->formatCarbonCopy($payload); //Fixes CCs into payload - $payload = $this->formatShorthandRecipients($payload); //Fixes shorthand recipients format + //Fixes shorthand recipients format - return $payload; + return $this->formatShorthandRecipients($payload); } /** @@ -46,9 +46,8 @@ public function formatPayload($payload) * * @return array - the modified request body */ - private function formatBlindCarbonCopy($payload) + private function formatBlindCarbonCopy(array $payload): array { - //If there's a list of BCC recipients, move them into the correct format if (isset($payload['bcc'])) { $payload = $this->addListToRecipients($payload, 'bcc'); @@ -64,16 +63,17 @@ private function formatBlindCarbonCopy($payload) * * @return array - the modified request body */ - private function formatCarbonCopy($payload) + private function formatCarbonCopy(array $payload): array { if (isset($payload['cc'])) { $ccAddresses = []; - for ($i = 0; $i < count($payload['cc']); ++$i) { - array_push($ccAddresses, $this->toAddressString($payload['cc'][$i]['address'])); + $counter = count($payload['cc']); + for ($i = 0; $i < $counter; ++$i) { + $ccAddresses[] = $this->toAddressString($payload['cc'][$i]['address']); } // set up the content headers as either what it was before or an empty array - $payload['content']['headers'] = isset($payload['content']['headers']) ? $payload['content']['headers'] : []; + $payload['content']['headers'] ??= []; // add cc header $payload['content']['headers']['CC'] = implode(',', $ccAddresses); @@ -89,14 +89,16 @@ private function formatCarbonCopy($payload) * @param array $payload - the request body * * @return array - the modified request body + * @throws \Exception */ - private function formatShorthandRecipients($payload) + private function formatShorthandRecipients(array $payload): array { if (isset($payload['content']['from'])) { $payload['content']['from'] = $this->toAddressObject($payload['content']['from']); } + $counter = count($payload['recipients']); - for ($i = 0; $i < count($payload['recipients']); ++$i) { + for ($i = 0; $i < $counter; ++$i) { $payload['recipients'][$i]['address'] = $this->toAddressObject($payload['recipients'][$i]['address']); } @@ -106,12 +108,13 @@ private function formatShorthandRecipients($payload) /** * Loops through the given listName in the payload and adds all the recipients to the recipients list after removing their names. * - * @param array $payload - the request body - * @param array $listName - the name of the array in the payload to be moved to the recipients list + * @param array $payload - the request body + * @param string $listName - the name of the array in the payload to be moved to the recipients list * * @return array - the modified request body + * @throws \Exception */ - private function addListToRecipients($payload, $listName) + private function addListToRecipients(array $payload, string $listName): array { $originalAddress = $this->toAddressString($payload['recipients'][0]['address']); foreach ($payload[$listName] as $recipient) { @@ -123,7 +126,7 @@ private function addListToRecipients($payload, $listName) unset($recipient['address']['name']); } - array_push($payload['recipients'], $recipient); + $payload['recipients'][] = $recipient; } //Delete the original object from the payload. @@ -138,8 +141,9 @@ private function addListToRecipients($payload, $listName) * @param $address - the shorthand form of an email address "Name " * * @return array - the longhand form of an email address [ "name" => "John", "email" => "john@exmmple.com" ] + * @throws \Exception */ - private function toAddressObject($address) + private function toAddressObject($address): array { $formatted = $address; if (is_string($formatted)) { @@ -152,7 +156,7 @@ private function toAddressObject($address) $formatted['name'] = $matches[1]; $formatted['email'] = $matches[2]; } else { - throw new \Exception('Invalid address format: '.$address); + throw new \Exception('Invalid address format: ' . $address); } } @@ -163,17 +167,12 @@ private function toAddressObject($address) * Takes the longhand form of an email address and converts it to the shorthand form. * * @param $address - the longhand form of an email address [ "name" => "John", "email" => "john@exmmple.com" ] - * @param string - the shorthand form of an email address "Name " */ - private function toAddressString($address) + private function toAddressString($address): string { // convert object to string if (!is_string($address)) { - if (isset($address['name'])) { - $address = '"'.$address['name'].'" <'.$address['email'].'>'; - } else { - $address = $address['email']; - } + $address = isset($address['name']) ? '"' . $address['name'] . '" <' . $address['email'] . '>' : $address['email']; } return $address; @@ -183,9 +182,9 @@ private function toAddressString($address) * Checks if a string is an email. * * @param string $email - a string that might be an email address - * @param bool - true if the given string is an email + * @return bool - true if the given string is an email */ - private function isEmail($email) + private function isEmail(string $email): bool { if (filter_var($email, FILTER_VALIDATE_EMAIL)) { return true; diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..55b2ebe --- /dev/null +++ b/rector.php @@ -0,0 +1,26 @@ +paths([ + __DIR__ . '/examples', + __DIR__ . '/lib', + __DIR__ . '/test', + ]); + + // register a single rule + $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); + + // define sets of rules + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_74, + SetList::TYPE_DECLARATION, + SetList::CODE_QUALITY, + ]); +}; diff --git a/test/unit/SparkPostResponseTest.php b/test/unit/SparkPostResponseTest.php index 90f16be..a42774a 100644 --- a/test/unit/SparkPostResponseTest.php +++ b/test/unit/SparkPostResponseTest.php @@ -11,38 +11,42 @@ class SparkPostResponseTest extends TestCase /** @var Mockery\MockInterface|\Psr\Http\Message\ResponseInterface */ private $responseMock; /** @var string */ - private $returnValue; + private string $returnValue; public function setUp(): void { $this->returnValue = 'some_value_to_return'; - $this->responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); + $this->responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); } - public function testGetProtocolVersion() + public function testGetProtocolVersion(): void { $this->responseMock->shouldReceive('getProtocolVersion')->andReturn($this->returnValue); $sparkpostResponse = new SparkPostResponse($this->responseMock); $this->assertEquals($this->responseMock->getProtocolVersion(), $sparkpostResponse->getProtocolVersion()); } - public function testWithProtocolVersion() + public function testWithProtocolVersion(): void { $param = 'protocol version'; + $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); - $this->responseMock->shouldReceive('withProtocolVersion')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('withProtocolVersion')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); - $this->assertEquals($this->responseMock->withProtocolVersion($param), $sparkpostResponse->withProtocolVersion($param)); + $this->assertEquals( + $this->responseMock->withProtocolVersion($param), + $sparkpostResponse->withProtocolVersion($param) + ); } - public function testGetHeaders() + public function testGetHeaders(): void { - $this->responseMock->shouldReceive('getHeaders')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('getHeaders')->andReturn([]); $sparkpostResponse = new SparkPostResponse($this->responseMock); $this->assertEquals($this->responseMock->getHeaders(), $sparkpostResponse->getHeaders()); } - public function testHasHeader() + public function testHasHeader(): void { $param = 'header'; @@ -51,16 +55,16 @@ public function testHasHeader() $this->assertEquals($this->responseMock->hasHeader($param), $sparkpostResponse->hasHeader($param)); } - public function testGetHeader() + public function testGetHeader(): void { $param = 'header'; - $this->responseMock->shouldReceive('getHeader')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('getHeader')->andReturn([]); $sparkpostResponse = new SparkPostResponse($this->responseMock); $this->assertEquals($this->responseMock->getHeader($param), $sparkpostResponse->getHeader($param)); } - public function testGetHeaderLine() + public function testGetHeaderLine(): void { $param = 'header'; @@ -69,36 +73,45 @@ public function testGetHeaderLine() $this->assertEquals($this->responseMock->getHeaderLine($param), $sparkpostResponse->getHeaderLine($param)); } - public function testWithHeader() + public function testWithHeader(): void { $param = 'header'; $param2 = 'value'; + $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); - $this->responseMock->shouldReceive('withHeader')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('withHeader')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); - $this->assertEquals($this->responseMock->withHeader($param, $param2), $sparkpostResponse->withHeader($param, $param2)); + $this->assertEquals( + $this->responseMock->withHeader($param, $param2), + $sparkpostResponse->withHeader($param, $param2) + ); } - public function testWithAddedHeader() + public function testWithAddedHeader(): void { $param = 'header'; $param2 = 'value'; + $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); - $this->responseMock->shouldReceive('withAddedHeader')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('withAddedHeader')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); - $this->assertEquals($this->responseMock->withAddedHeader($param, $param2), $sparkpostResponse->withAddedHeader($param, $param2)); + $this->assertEquals( + $this->responseMock->withAddedHeader($param, $param2), + $sparkpostResponse->withAddedHeader($param, $param2) + ); } - public function testWithoutHeader() + public function testWithoutHeader(): void { $param = 'header'; + $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); - $this->responseMock->shouldReceive('withoutHeader')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('withoutHeader')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); $this->assertEquals($this->responseMock->withoutHeader($param), $sparkpostResponse->withoutHeader($param)); } - public function testGetRequest() + public function testGetRequest(): void { $request = ['some' => 'request']; $this->responseMock->shouldReceive('getRequest')->andReturn($request); @@ -106,32 +119,33 @@ public function testGetRequest() $this->assertEquals($sparkpostResponse->getRequest(), $request); } - public function testWithBody() + public function testWithBody(): void { - $param = Mockery::mock('Psr\Http\Message\StreamInterface'); + $param = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); - $this->responseMock->shouldReceive('withBody')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('withBody')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); $this->assertEquals($this->responseMock->withBody($param), $sparkpostResponse->withBody($param)); } - public function testGetStatusCode() + public function testGetStatusCode(): void { - $this->responseMock->shouldReceive('getStatusCode')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('getStatusCode')->andReturn(200); $sparkpostResponse = new SparkPostResponse($this->responseMock); $this->assertEquals($this->responseMock->getStatusCode(), $sparkpostResponse->getStatusCode()); } - public function testWithStatus() + public function testWithStatus(): void { - $param = 'status'; + $param = 200; - $this->responseMock->shouldReceive('withStatus')->andReturn($this->returnValue); + $this->responseMock->shouldReceive('withStatus')->andReturn($this->responseMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); $this->assertEquals($this->responseMock->withStatus($param), $sparkpostResponse->withStatus($param)); } - public function testGetReasonPhrase() + public function testGetReasonPhrase(): void { $this->responseMock->shouldReceive('getReasonPhrase')->andReturn($this->returnValue); $sparkpostResponse = new SparkPostResponse($this->responseMock); diff --git a/test/unit/SparkPostTest.php b/test/unit/SparkPostTest.php index d707ced..de884e9 100644 --- a/test/unit/SparkPostTest.php +++ b/test/unit/SparkPostTest.php @@ -16,19 +16,21 @@ class SparkPostTest extends TestCase { + public $badResponseBody; + public $badResponseMock; private $clientMock; /** @var SparkPost */ - private $resource; + private \SparkPost\SparkPost $resource; private $exceptionMock; - private $exceptionBody; + private array $exceptionBody; private $responseMock; - private $responseBody; + private array $responseBody; private $promiseMock; - private $postTransmissionPayload = [ + private array $postTransmissionPayload = [ 'content' => [ 'from' => ['name' => 'Sparkpost Team', 'email' => 'postmaster@sendmailfor.me'], 'subject' => 'First Mailing From PHP', @@ -40,43 +42,45 @@ class SparkPostTest extends TestCase ], ]; - private $getTransmissionPayload = [ + private array $getTransmissionPayload = [ 'campaign_id' => 'thanksgiving', ]; public function setUp(): void { // response mock up - $responseBodyMock = Mockery::mock(); + $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $this->responseBody = ['results' => 'yay']; - $this->responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); + $this->responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); $this->responseMock->shouldReceive('getStatusCode')->andReturn(200); $this->responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->responseBody)); - $errorBodyMock = Mockery::mock(); + $errorBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $this->badResponseBody = ['errors' => []]; - $this->badResponseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); + $this->badResponseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); $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(); + $exceptionResponseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $exceptionResponseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $this->exceptionBody = ['results' => 'failed']; - $this->exceptionMock = Mockery::mock('Http\Client\Exception\HttpException'); + $this->exceptionMock = Mockery::mock(\Http\Client\Exception\HttpException::class); $this->exceptionMock->shouldReceive('getResponse')->andReturn($exceptionResponseMock); $exceptionResponseMock->shouldReceive('getStatusCode')->andReturn(500); - $exceptionResponseMock->shouldReceive('getBody->__toString')->andReturn(json_encode($this->exceptionBody)); + $exceptionResponseMock->shouldReceive('getBody')->andReturn($exceptionResponseBodyMock); + $exceptionResponseBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->exceptionBody)); // promise mock up - $this->promiseMock = Mockery::mock('Http\Promise\Promise'); + $this->promiseMock = Mockery::mock(\Http\Promise\Promise::class); //setup mock for the adapter - $this->clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $this->clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); $this->clientMock->shouldReceive('sendAsyncRequest')-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($this->promiseMock); + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andReturn($this->promiseMock); $this->resource = new SparkPost($this->clientMock, ['key' => 'SPARKPOST_API_KEY']); } @@ -86,24 +90,31 @@ public function tearDown(): void Mockery::close(); } - public function testRequestSync() + public function testRequestSync(): void { $this->resource->setOptions(['async' => false]); $this->clientMock->shouldReceive('sendRequest')->andReturn($this->responseMock); - $this->assertInstanceOf('SparkPost\SparkPostResponse', $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload)); + $this->assertInstanceOf( + \SparkPost\SparkPostResponse::class, + $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload) + ); } - public function testRequestAsync() + public function testRequestAsync(): void { - $promiseMock = Mockery::mock('Http\Promise\Promise'); + $promiseMock = Mockery::mock(\Http\Promise\Promise::class); $this->resource->setOptions(['async' => true]); $this->clientMock->shouldReceive('sendAsyncRequest')->andReturn($promiseMock); - $this->assertInstanceOf('SparkPost\SparkPostPromise', $this->resource->request('GET', 'transmissions', $this->getTransmissionPayload)); + $this->assertInstanceOf( + \SparkPost\SparkPostPromise::class, + $this->resource->request('GET', 'transmissions', $this->getTransmissionPayload) + ); } - public function testDebugOptionWhenFalse() { + public function testDebugOptionWhenFalse(): void + { $this->resource->setOptions(['async' => false, 'debug' => false]); $this->clientMock->shouldReceive('sendRequest')->andReturn($this->responseMock); @@ -112,7 +123,8 @@ public function testDebugOptionWhenFalse() { $this->assertEquals($response->getRequest(), null); } - public function testDebugOptionWhenTrue() { + public function testDebugOptionWhenTrue(): void + { // setup $this->resource->setOptions(['async' => false, 'debug' => true]); @@ -126,31 +138,30 @@ public function testDebugOptionWhenTrue() { try { $response = $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload); - } - catch (\Exception $e) { + } catch (\Exception $e) { $this->assertEquals(json_decode($e->getRequest()['body'], true), $this->postTransmissionPayload); } } - public function testSuccessfulSyncRequest() + public function testSuccessfulSyncRequest(): void { $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($this->responseMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andReturn($this->responseMock); $response = $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); - $this->assertEquals($this->responseBody, $response->getBody()); + $this->assertEquals($this->responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testUnsuccessfulSyncRequest() + public function testUnsuccessfulSyncRequest(): void { $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andThrow($this->exceptionMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andThrow($this->exceptionMock); try { $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); @@ -160,25 +171,25 @@ public function testUnsuccessfulSyncRequest() } } - public function testSuccessfulSyncRequestWithRetries() + public function testSuccessfulSyncRequestWithRetries(): void { $this->clientMock->shouldReceive('sendRequest')-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($this->badResponseMock, $this->badResponseMock, $this->responseMock); + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + 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($this->responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testUnsuccessfulSyncRequestWithRetries() + public function testUnsuccessfulSyncRequestWithRetries(): void { $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andThrow($this->exceptionMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andThrow($this->exceptionMock); $this->resource->setOptions(['retries' => 2]); try { @@ -189,18 +200,18 @@ public function testUnsuccessfulSyncRequestWithRetries() } } - public function testSuccessfulAsyncRequestWithWait() + public function testSuccessfulAsyncRequestWithWait(): void { $this->promiseMock->shouldReceive('wait')->andReturn($this->responseMock); $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); $response = $promise->wait(); - $this->assertEquals($this->responseBody, $response->getBody()); + $this->assertEquals($this->responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testUnsuccessfulAsyncRequestWithWait() + public function testUnsuccessfulAsyncRequestWithWait(): void { $this->promiseMock->shouldReceive('wait')->andThrow($this->exceptionMock); @@ -214,7 +225,7 @@ public function testUnsuccessfulAsyncRequestWithWait() } } - public function testSuccessfulAsyncRequestWithThen() + public function testSuccessfulAsyncRequestWithThen(): void { $guzzlePromise = new GuzzleFulfilledPromise($this->responseMock); $result = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); @@ -222,13 +233,13 @@ public function testSuccessfulAsyncRequestWithThen() $promise = new SparkPostPromise(new GuzzleAdapterPromise($guzzlePromise, $result)); $responseBody = $this->responseBody; - $promise->then(function ($response) use ($responseBody) { + $promise->then(function ($response) use ($responseBody): void { $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals($responseBody, $response->getBody()); + $this->assertEquals($responseBody, $response->getBodyDecoded()); }, null)->wait(); } - public function testUnsuccessfulAsyncRequestWithThen() + public function testUnsuccessfulAsyncRequestWithThen(): void { $guzzlePromise = new GuzzleRejectedPromise($this->exceptionMock); $result = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); @@ -236,53 +247,53 @@ public function testUnsuccessfulAsyncRequestWithThen() $promise = new SparkPostPromise(new GuzzleAdapterPromise($guzzlePromise, $result)); $exceptionBody = $this->exceptionBody; - $promise->then(null, function ($exception) use ($exceptionBody) { + $promise->then(null, function ($exception) use ($exceptionBody): void { $this->assertEquals(500, $exception->getCode()); $this->assertEquals($exceptionBody, $exception->getBody()); })->wait(); } - public function testSuccessfulAsyncRequestWithRetries() + public function testSuccessfulAsyncRequestWithRetries(): void { $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); - $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); $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) - ); + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + 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) { + $promise->then(function ($resp): void { $this->assertEquals(200, $resp->getStatusCode()); })->wait(); } - public function testUnsuccessfulAsyncRequestWithRetries() + public function testUnsuccessfulAsyncRequestWithRetries(): void { $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); $rejectedPromise = new GuzzleRejectedPromise($this->exceptionMock); - $clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); $clientMock->shouldReceive('sendAsyncRequest')-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn(new GuzzleAdapterPromise($rejectedPromise, $testReq)); + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + 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) { + $promise->then(null, function ($exception): void { $this->assertEquals(500, $exception->getCode()); $this->assertEquals($this->exceptionBody, $exception->getBody()); })->wait(); } - public function testPromise() + public function testPromise(): void { $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); @@ -293,16 +304,16 @@ public function testPromise() $this->assertEquals($this->promiseMock->getState(), $promise->getState()); } - public function testUnsupportedAsyncRequest() + public function testUnsupportedAsyncRequest(): void { $this->expectException(\Exception::class); - $this->resource->setHttpClient(Mockery::mock('Http\Client\HttpClient')); + $this->resource->setHttpClient(Mockery::mock(\Http\Client\HttpClient::class)); $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); } - public function testGetHttpHeaders() + public function testGetHttpHeaders(): void { $headers = $this->resource->getHttpHeaders([ 'Custom-Header' => 'testing', @@ -313,45 +324,45 @@ public function testGetHttpHeaders() $this->assertEquals('SPARKPOST_API_KEY', $headers['Authorization']); $this->assertEquals('application/json', $headers['Content-Type']); $this->assertEquals('testing', $headers['Custom-Header']); - $this->assertEquals('php-sparkpost/'.$version, $headers['User-Agent']); + $this->assertEquals('php-sparkpost/' . $version, $headers['User-Agent']); } - public function testGetUrl() + public function testGetUrl(): void { $url = 'https://api.sparkpost.com:443/api/v1/transmissions?key=value 1,value 2,value 3'; $testUrl = $this->resource->getUrl('transmissions', ['key' => ['value 1', 'value 2', 'value 3']]); $this->assertEquals($url, $testUrl); } - public function testSetHttpClient() + public function testSetHttpClient(): void { $mock = Mockery::mock(HttpClient::class); $this->resource->setHttpClient($mock); $this->assertEquals($mock, NSA::getProperty($this->resource, 'httpClient')); } - public function testSetHttpAsyncClient() + public function testSetHttpAsyncClient(): void { $mock = Mockery::mock(HttpAsyncClient::class); $this->resource->setHttpClient($mock); $this->assertEquals($mock, NSA::getProperty($this->resource, 'httpClient')); } - public function testSetHttpClientException() + public function testSetHttpClientException(): void { $this->expectException(\Exception::class); $this->resource->setHttpClient(new \stdClass()); } - public function testSetOptionsStringKey() + public function testSetOptionsStringKey(): void { $this->resource->setOptions('SPARKPOST_API_KEY'); $options = NSA::getProperty($this->resource, 'options'); $this->assertEquals('SPARKPOST_API_KEY', $options['key']); } - public function testSetBadOptions() + public function testSetBadOptions(): void { $this->expectException(\Exception::class); @@ -359,7 +370,7 @@ public function testSetBadOptions() $this->resource->setOptions(['not' => 'SPARKPOST_API_KEY']); } - public function testSetMessageFactory() + public function testSetMessageFactory(): void { $messageFactory = Mockery::mock(MessageFactory::class); $this->resource->setMessageFactory($messageFactory); diff --git a/test/unit/TransmissionTest.php b/test/unit/TransmissionTest.php index 09bd1cb..3be8534 100644 --- a/test/unit/TransmissionTest.php +++ b/test/unit/TransmissionTest.php @@ -10,9 +10,9 @@ class TransmissionTest extends TestCase { private $clientMock; /** @var SparkPost */ - private $resource; + private \SparkPost\SparkPost $resource; - private $postTransmissionPayload = [ + private array $postTransmissionPayload = [ 'content' => [ 'from' => ['name' => 'Sparkpost Team', 'email' => 'postmaster@sendmailfor.me'], 'subject' => 'First Mailing From PHP', @@ -41,7 +41,7 @@ class TransmissionTest extends TestCase ]; - private $getTransmissionPayload = [ + private array $getTransmissionPayload = [ 'campaign_id' => 'thanksgiving', ]; @@ -55,7 +55,7 @@ class TransmissionTest extends TestCase public function setUp(): void { //setup mock for the adapter - $this->clientMock = Mockery::mock('Http\Adapter\Guzzle6\Client'); + $this->clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); $this->resource = new SparkPost($this->clientMock, ['key' => 'SPARKPOST_API_KEY', 'async' => false]); } @@ -65,7 +65,7 @@ public function tearDown(): void Mockery::close(); } - public function testInvalidEmailFormat() + public function testInvalidEmailFormat(): void { $this->expectException(\Exception::class); @@ -76,17 +76,17 @@ public function testInvalidEmailFormat() $response = $this->resource->transmissions->post($this->postTransmissionPayload); } - public function testGet() + public function testGet(): void { - $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); - $responseBodyMock = Mockery::mock(); + $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($responseMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); @@ -94,21 +94,21 @@ public function testGet() $response = $this->resource->transmissions->get($this->getTransmissionPayload); - $this->assertEquals($responseBody, $response->getBody()); + $this->assertEquals($responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testPut() + public function testPut(): void { - $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); - $responseBodyMock = Mockery::mock(); + $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($responseMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); @@ -116,21 +116,21 @@ public function testPut() $response = $this->resource->transmissions->put($this->getTransmissionPayload); - $this->assertEquals($responseBody, $response->getBody()); + $this->assertEquals($responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testPost() + public function testPost(): void { - $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); - $responseBodyMock = Mockery::mock(); + $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($responseMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); @@ -138,46 +138,46 @@ public function testPost() $response = $this->resource->transmissions->post($this->postTransmissionPayload); - $this->assertEquals($responseBody, $response->getBody()); + $this->assertEquals($responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testPostWithRecipientList() + public function testPostWithRecipientList(): void { $postTransmissionPayload = $this->postTransmissionPayload; $postTransmissionPayload['recipients'] = ['list_id' => 'SOME_LIST_ID']; - $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); - $responseBodyMock = Mockery::mock(); + $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($responseMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($responseBody)); - $response = $this->resource->transmissions->post(); + $response = $this->resource->transmissions->post($postTransmissionPayload); - $this->assertEquals($responseBody, $response->getBody()); + $this->assertEquals($responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testDelete() + public function testDelete(): void { - $responseMock = Mockery::mock('Psr\Http\Message\ResponseInterface'); - $responseBodyMock = Mockery::mock(); + $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> - once()-> - with(Mockery::type('GuzzleHttp\Psr7\Request'))-> - andReturn($responseMock); + once()-> + with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); $responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); @@ -185,13 +185,16 @@ public function testDelete() $response = $this->resource->transmissions->delete($this->getTransmissionPayload); - $this->assertEquals($responseBody, $response->getBody()); + $this->assertEquals($responseBody, $response->getBodyDecoded()); $this->assertEquals(200, $response->getStatusCode()); } - public function testFormatPayload() + public function testFormatPayload(): void { - $correctFormattedPayload = json_decode('{"content":{"from":{"name":"Sparkpost Team","email":"postmaster@sendmailfor.me"},"subject":"First Mailing From PHP","text":"Congratulations, {{name}}!! You just sent your very first mailing!","headers":{"CC":"avi.goldman@sparkpost.com"}},"substitution_data":{"name":"Avi"},"recipients":[{"address":{"name":"Vincent","email":"vincent.song@sparkpost.com"}},{"address":{"email":"test@example.com"}},{"address":{"email":"emely.giraldo@sparkpost.com","header_to":"\"Vincent\" "}},{"address":{"email":"avi.goldman@sparkpost.com","header_to":"\"Vincent\" "}}]}', true); + $correctFormattedPayload = json_decode( + '{"content":{"from":{"name":"Sparkpost Team","email":"postmaster@sendmailfor.me"},"subject":"First Mailing From PHP","text":"Congratulations, {{name}}!! You just sent your very first mailing!","headers":{"CC":"avi.goldman@sparkpost.com"}},"substitution_data":{"name":"Avi"},"recipients":[{"address":{"name":"Vincent","email":"vincent.song@sparkpost.com"}},{"address":{"email":"test@example.com"}},{"address":{"email":"emely.giraldo@sparkpost.com","header_to":"\"Vincent\" "}},{"address":{"email":"avi.goldman@sparkpost.com","header_to":"\"Vincent\" "}}]}', + true + ); $formattedPayload = $this->resource->transmissions->formatPayload($this->postTransmissionPayload); $this->assertEquals($correctFormattedPayload, $formattedPayload); From a723681279ce7063bdbc95f544719747d6321934 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Thu, 12 Feb 2026 20:54:41 +0000 Subject: [PATCH 04/12] fixes for composer audit warnings --- composer.json | 5 +- lib/SparkPost/SparkPost.php | 66 +++++++++--- test/unit/SparkPostResponseTest.php | 19 ++-- test/unit/SparkPostTest.php | 151 ++++++++++++++++++++-------- test/unit/TransmissionTest.php | 49 +++++---- 5 files changed, 207 insertions(+), 83 deletions(-) diff --git a/composer.json b/composer.json index a3d9b41..65a7525 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "require": { "php": "^7.4 || ^8.0", "php-http/httplug": "^1.0 || ^2.0", - "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "nyholm/psr7": "^1.0", "php-http/message": "^1.0", "php-http/client-implementation": "^1.0", "php-http/discovery": "^1.0", @@ -29,7 +30,7 @@ "require-dev": { "phpunit/phpunit": "^8.0 || ^9.0", "rector/rector": "^1.0", - "php-http/guzzle6-adapter": "^1.0", + "php-http/guzzle7-adapter": "^1.0", "mockery/mockery": "^1.3", "nyholm/nsa": "^1.0", "php-coveralls/php-coveralls": "^2.4", diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index f346877..5dd429a 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -5,9 +5,10 @@ use Http\Client\Exception; use Http\Client\HttpClient; use Http\Client\HttpAsyncClient; -use Http\Discovery\MessageFactoryDiscovery; -use Http\Message\RequestFactory; +use Http\Discovery\Psr17FactoryDiscovery; +use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\StreamFactoryInterface; class SparkPost { @@ -22,9 +23,14 @@ class SparkPost private $httpClient; /** - * @var RequestFactory + * @var RequestFactoryInterface */ - private RequestFactory $messageFactory; + private RequestFactoryInterface $requestFactory; + + /** + * @var StreamFactoryInterface + */ + private StreamFactoryInterface $streamFactory; /** * @var array Options for requests @@ -223,7 +229,17 @@ public function buildRequestValues(string $method, string $uri, array $payload, */ public function buildRequestInstance($method, $url, $headers, $body): RequestInterface { - return $this->getMessageFactory()->createRequest($method, $url, $headers, $body); + $request = $this->getRequestFactory()->createRequest($method, $url); + + foreach ($headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + if (isset($body) && $body !== '' && $body !== []) { + $request = $request->withBody($this->getStreamFactory()->createStream($body)); + } + + return $request; } /** @@ -368,25 +384,49 @@ private function setupEndpoints(): void } /** - * @return RequestFactory + * @return RequestFactoryInterface + */ + private function getRequestFactory(): RequestFactoryInterface + { + if (!isset($this->requestFactory)) { + $this->requestFactory = Psr17FactoryDiscovery::findRequestFactory(); + } + + return $this->requestFactory; + } + + /** + * @return StreamFactoryInterface */ - private function getMessageFactory(): RequestFactory + private function getStreamFactory(): StreamFactoryInterface { - if (!isset($this->messageFactory)) { - $this->messageFactory = MessageFactoryDiscovery::find(); + if (!isset($this->streamFactory)) { + $this->streamFactory = Psr17FactoryDiscovery::findStreamFactory(); } - return $this->messageFactory; + return $this->streamFactory; + } + + /** + * @param RequestFactoryInterface $requestFactory + * + * @return SparkPost + */ + public function setRequestFactory(RequestFactoryInterface $requestFactory): self + { + $this->requestFactory = $requestFactory; + + return $this; } /** - * @param RequestFactory $messageFactory + * @param StreamFactoryInterface $streamFactory * * @return SparkPost */ - public function setMessageFactory(RequestFactory $messageFactory): self + public function setStreamFactory(StreamFactoryInterface $streamFactory): self { - $this->messageFactory = $messageFactory; + $this->streamFactory = $streamFactory; return $this; } diff --git a/test/unit/SparkPostResponseTest.php b/test/unit/SparkPostResponseTest.php index a42774a..7fe60a0 100644 --- a/test/unit/SparkPostResponseTest.php +++ b/test/unit/SparkPostResponseTest.php @@ -3,12 +3,15 @@ namespace SparkPost\Test; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; use SparkPost\SparkPostResponse; use Mockery; class SparkPostResponseTest extends TestCase { - /** @var Mockery\MockInterface|\Psr\Http\Message\ResponseInterface */ + /** @var Mockery\MockInterface|ResponseInterface */ private $responseMock; /** @var string */ private string $returnValue; @@ -16,7 +19,7 @@ class SparkPostResponseTest extends TestCase public function setUp(): void { $this->returnValue = 'some_value_to_return'; - $this->responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $this->responseMock = Mockery::mock(ResponseInterface::class); } public function testGetProtocolVersion(): void @@ -29,7 +32,7 @@ public function testGetProtocolVersion(): void public function testWithProtocolVersion(): void { $param = 'protocol version'; - $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); + $messageMock = Mockery::mock(MessageInterface::class); $this->responseMock->shouldReceive('withProtocolVersion')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); @@ -77,7 +80,7 @@ public function testWithHeader(): void { $param = 'header'; $param2 = 'value'; - $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); + $messageMock = Mockery::mock(MessageInterface::class); $this->responseMock->shouldReceive('withHeader')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); @@ -91,7 +94,7 @@ public function testWithAddedHeader(): void { $param = 'header'; $param2 = 'value'; - $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); + $messageMock = Mockery::mock(MessageInterface::class); $this->responseMock->shouldReceive('withAddedHeader')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); @@ -104,7 +107,7 @@ public function testWithAddedHeader(): void public function testWithoutHeader(): void { $param = 'header'; - $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); + $messageMock = Mockery::mock(MessageInterface::class); $this->responseMock->shouldReceive('withoutHeader')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); @@ -121,8 +124,8 @@ public function testGetRequest(): void public function testWithBody(): void { - $param = Mockery::mock(\Psr\Http\Message\StreamInterface::class); - $messageMock = Mockery::mock(\Psr\Http\Message\MessageInterface::class); + $param = Mockery::mock(StreamInterface::class); + $messageMock = Mockery::mock(MessageInterface::class); $this->responseMock->shouldReceive('withBody')->andReturn($messageMock); $sparkpostResponse = new SparkPostResponse($this->responseMock); diff --git a/test/unit/SparkPostTest.php b/test/unit/SparkPostTest.php index de884e9..952d911 100644 --- a/test/unit/SparkPostTest.php +++ b/test/unit/SparkPostTest.php @@ -2,25 +2,33 @@ namespace SparkPost\Test; +use Http\Client\Exception; +use Http\Client\Exception\HttpException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; -use Http\Message\MessageFactory; +use Http\Promise\Promise; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; use Nyholm\NSA; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; use SparkPost\SparkPost; +use SparkPost\SparkPostException; use SparkPost\SparkPostPromise; use GuzzleHttp\Promise\FulfilledPromise as GuzzleFulfilledPromise; use GuzzleHttp\Promise\RejectedPromise as GuzzleRejectedPromise; -use Http\Adapter\Guzzle6\Promise as GuzzleAdapterPromise; +use Http\Adapter\Guzzle7\Promise as GuzzleAdapterPromise; use Mockery; +use SparkPost\SparkPostResponse; class SparkPostTest extends TestCase { - public $badResponseBody; + public array $badResponseBody; public $badResponseMock; private $clientMock; /** @var SparkPost */ - private \SparkPost\SparkPost $resource; + private SparkPost $resource; private $exceptionMock; private array $exceptionBody; @@ -46,41 +54,44 @@ class SparkPostTest extends TestCase 'campaign_id' => 'thanksgiving', ]; + /** + * @throws \Exception + */ public function setUp(): void { // response mock up - $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $responseBodyMock = Mockery::mock(StreamInterface::class); $this->responseBody = ['results' => 'yay']; - $this->responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $this->responseMock = Mockery::mock(ResponseInterface::class); $this->responseMock->shouldReceive('getStatusCode')->andReturn(200); $this->responseMock->shouldReceive('getBody')->andReturn($responseBodyMock); $responseBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->responseBody)); - $errorBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $errorBodyMock = Mockery::mock(StreamInterface::class); $this->badResponseBody = ['errors' => []]; - $this->badResponseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); + $this->badResponseMock = Mockery::mock(ResponseInterface::class); $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(\Psr\Http\Message\ResponseInterface::class); - $exceptionResponseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $exceptionResponseMock = Mockery::mock(ResponseInterface::class); + $exceptionResponseBodyMock = Mockery::mock(StreamInterface::class); $this->exceptionBody = ['results' => 'failed']; - $this->exceptionMock = Mockery::mock(\Http\Client\Exception\HttpException::class); + $this->exceptionMock = Mockery::mock(HttpException::class); $this->exceptionMock->shouldReceive('getResponse')->andReturn($exceptionResponseMock); $exceptionResponseMock->shouldReceive('getStatusCode')->andReturn(500); $exceptionResponseMock->shouldReceive('getBody')->andReturn($exceptionResponseBodyMock); $exceptionResponseBodyMock->shouldReceive('__toString')->andReturn(json_encode($this->exceptionBody)); // promise mock up - $this->promiseMock = Mockery::mock(\Http\Promise\Promise::class); + $this->promiseMock = Mockery::mock(Promise::class); //setup mock for the adapter - $this->clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); - $this->clientMock->shouldReceive('sendAsyncRequest')-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> - andReturn($this->promiseMock); + $this->clientMock = Mockery::mock(HttpClient::class, HttpAsyncClient::class); + $this->clientMock->shouldReceive('sendAsyncRequest') + ->with(Mockery::type(RequestInterface::class)) + ->andReturn($this->promiseMock); $this->resource = new SparkPost($this->clientMock, ['key' => 'SPARKPOST_API_KEY']); } @@ -90,29 +101,41 @@ public function tearDown(): void Mockery::close(); } + /** + * @throws SparkPostException + * @throws \Exception + */ public function testRequestSync(): void { $this->resource->setOptions(['async' => false]); $this->clientMock->shouldReceive('sendRequest')->andReturn($this->responseMock); $this->assertInstanceOf( - \SparkPost\SparkPostResponse::class, + SparkPostResponse::class, $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload) ); } + /** + * @throws SparkPostException + * @throws \Exception + */ public function testRequestAsync(): void { - $promiseMock = Mockery::mock(\Http\Promise\Promise::class); + $promiseMock = Mockery::mock(Promise::class); $this->resource->setOptions(['async' => true]); $this->clientMock->shouldReceive('sendAsyncRequest')->andReturn($promiseMock); $this->assertInstanceOf( - \SparkPost\SparkPostPromise::class, + SparkPostPromise::class, $this->resource->request('GET', 'transmissions', $this->getTransmissionPayload) ); } + /** + * @throws SparkPostException + * @throws \Exception + */ public function testDebugOptionWhenFalse(): void { $this->resource->setOptions(['async' => false, 'debug' => false]); @@ -120,9 +143,13 @@ public function testDebugOptionWhenFalse(): void $response = $this->resource->request('POST', 'transmissions', $this->postTransmissionPayload); - $this->assertEquals($response->getRequest(), null); + $this->assertEquals(null, $response->getRequest()); } + /** + * @throws SparkPostException + * @throws \Exception + */ public function testDebugOptionWhenTrue(): void { // setup @@ -143,11 +170,15 @@ public function testDebugOptionWhenTrue(): void } } + /** + * @throws Exception + * @throws SparkPostException + */ public function testSuccessfulSyncRequest(): void { $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andReturn($this->responseMock); $response = $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); @@ -160,21 +191,26 @@ public function testUnsuccessfulSyncRequest(): void { $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andThrow($this->exceptionMock); try { $this->resource->syncRequest('POST', 'transmissions', $this->postTransmissionPayload); - } catch (\Exception $e) { + } catch (\Exception|Exception $e) { $this->assertEquals($this->exceptionBody, $e->getBody()); $this->assertEquals(500, $e->getCode()); } } + /** + * @throws Exception + * @throws SparkPostException + * @throws \Exception + */ public function testSuccessfulSyncRequestWithRetries(): void { $this->clientMock->shouldReceive('sendRequest')-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andReturn($this->badResponseMock, $this->badResponseMock, $this->responseMock); $this->resource->setOptions(['retries' => 2]); @@ -184,11 +220,15 @@ public function testSuccessfulSyncRequestWithRetries(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * @throws Exception + * @throws \Exception + */ public function testUnsuccessfulSyncRequestWithRetries(): void { $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andThrow($this->exceptionMock); $this->resource->setOptions(['retries' => 2]); @@ -200,6 +240,10 @@ public function testUnsuccessfulSyncRequestWithRetries(): void } } + /** + * @throws SparkPostException + * @throws \Exception + */ public function testSuccessfulAsyncRequestWithWait(): void { $this->promiseMock->shouldReceive('wait')->andReturn($this->responseMock); @@ -211,6 +255,9 @@ public function testSuccessfulAsyncRequestWithWait(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * @throws \Exception + */ public function testUnsuccessfulAsyncRequestWithWait(): void { $this->promiseMock->shouldReceive('wait')->andThrow($this->exceptionMock); @@ -225,6 +272,9 @@ public function testUnsuccessfulAsyncRequestWithWait(): void } } + /** + * @throws \Throwable + */ public function testSuccessfulAsyncRequestWithThen(): void { $guzzlePromise = new GuzzleFulfilledPromise($this->responseMock); @@ -236,9 +286,12 @@ public function testSuccessfulAsyncRequestWithThen(): void $promise->then(function ($response) use ($responseBody): void { $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals($responseBody, $response->getBodyDecoded()); - }, null)->wait(); + })->wait(); } + /** + * @throws \Throwable + */ public function testUnsuccessfulAsyncRequestWithThen(): void { $guzzlePromise = new GuzzleRejectedPromise($this->exceptionMock); @@ -253,17 +306,20 @@ public function testUnsuccessfulAsyncRequestWithThen(): void })->wait(); } + /** + * @throws \Throwable + */ public function testSuccessfulAsyncRequestWithRetries(): void { $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); - $clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); - $clientMock->shouldReceive('sendAsyncRequest')-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> - andReturn( - new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), - new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->badResponseMock), $testReq), - new GuzzleAdapterPromise(new GuzzleFulfilledPromise($this->responseMock), $testReq) - ); + $clientMock = Mockery::mock(HttpClient::class, HttpAsyncClient::class); + $clientMock->shouldReceive('sendAsyncRequest') + ->with(Mockery::type(RequestInterface::class)) + ->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']); @@ -274,14 +330,17 @@ public function testSuccessfulAsyncRequestWithRetries(): void })->wait(); } + /** + * @throws \Throwable + */ public function testUnsuccessfulAsyncRequestWithRetries(): void { $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); $rejectedPromise = new GuzzleRejectedPromise($this->exceptionMock); - $clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); - $clientMock->shouldReceive('sendAsyncRequest')-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> - andReturn(new GuzzleAdapterPromise($rejectedPromise, $testReq)); + $clientMock = Mockery::mock(HttpClient::class, HttpAsyncClient::class); + $clientMock->shouldReceive('sendAsyncRequest') + ->with(Mockery::type(RequestInterface::class)) + ->andReturn(new GuzzleAdapterPromise($rejectedPromise, $testReq)); $resource = new SparkPost($clientMock, ['key' => 'SPARKPOST_API_KEY']); @@ -293,6 +352,9 @@ public function testUnsuccessfulAsyncRequestWithRetries(): void })->wait(); } + /** + * @throws \Exception + */ public function testPromise(): void { $promise = $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); @@ -308,7 +370,7 @@ public function testUnsupportedAsyncRequest(): void { $this->expectException(\Exception::class); - $this->resource->setHttpClient(Mockery::mock(\Http\Client\HttpClient::class)); + $this->resource->setHttpClient(Mockery::mock(HttpClient::class)); $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); } @@ -355,6 +417,9 @@ public function testSetHttpClientException(): void $this->resource->setHttpClient(new \stdClass()); } + /** + * @throws \Exception + */ public function testSetOptionsStringKey(): void { $this->resource->setOptions('SPARKPOST_API_KEY'); @@ -370,11 +435,11 @@ public function testSetBadOptions(): void $this->resource->setOptions(['not' => 'SPARKPOST_API_KEY']); } - public function testSetMessageFactory(): void + public function testSetRequestFactory(): void { - $messageFactory = Mockery::mock(MessageFactory::class); - $this->resource->setMessageFactory($messageFactory); + $messageFactory = Mockery::mock(RequestFactoryInterface::class); + $this->resource->setRequestFactory($messageFactory); - $this->assertEquals($messageFactory, NSA::invokeMethod($this->resource, 'getMessageFactory')); + $this->assertEquals($messageFactory, NSA::invokeMethod($this->resource, 'getRequestFactory')); } } diff --git a/test/unit/TransmissionTest.php b/test/unit/TransmissionTest.php index 3be8534..4424d6f 100644 --- a/test/unit/TransmissionTest.php +++ b/test/unit/TransmissionTest.php @@ -3,14 +3,19 @@ namespace SparkPost\Test; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; use SparkPost\SparkPost; use Mockery; +use Psr\Http\Message\RequestInterface; +use Http\Client\HttpClient; +use SparkPost\SparkPostException; class TransmissionTest extends TestCase { private $clientMock; /** @var SparkPost */ - private \SparkPost\SparkPost $resource; + private SparkPost $resource; private array $postTransmissionPayload = [ 'content' => [ @@ -50,12 +55,13 @@ class TransmissionTest extends TestCase * * @before * + * @throws \Exception * @see PHPUnit_Framework_TestCase::setUp() */ public function setUp(): void { //setup mock for the adapter - $this->clientMock = Mockery::mock(\Http\Adapter\Guzzle6\Client::class); + $this->clientMock = Mockery::mock(HttpClient::class); $this->resource = new SparkPost($this->clientMock, ['key' => 'SPARKPOST_API_KEY', 'async' => false]); } @@ -76,16 +82,19 @@ public function testInvalidEmailFormat(): void $response = $this->resource->transmissions->post($this->postTransmissionPayload); } + /** + * @throws SparkPostException + */ public function testGet(): void { - $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); - $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $responseMock = Mockery::mock(ResponseInterface::class); + $responseBodyMock = Mockery::mock(StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); @@ -98,16 +107,19 @@ public function testGet(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * @throws SparkPostException + */ public function testPut(): void { - $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); - $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $responseMock = Mockery::mock(ResponseInterface::class); + $responseBodyMock = Mockery::mock(StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); @@ -122,14 +134,14 @@ public function testPut(): void public function testPost(): void { - $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); - $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $responseMock = Mockery::mock(ResponseInterface::class); + $responseBodyMock = Mockery::mock(StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); @@ -147,14 +159,14 @@ public function testPostWithRecipientList(): void $postTransmissionPayload = $this->postTransmissionPayload; $postTransmissionPayload['recipients'] = ['list_id' => 'SOME_LIST_ID']; - $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); - $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $responseMock = Mockery::mock(ResponseInterface::class); + $responseBodyMock = Mockery::mock(StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); @@ -167,16 +179,19 @@ public function testPostWithRecipientList(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * @throws SparkPostException + */ public function testDelete(): void { - $responseMock = Mockery::mock(\Psr\Http\Message\ResponseInterface::class); - $responseBodyMock = Mockery::mock(\Psr\Http\Message\StreamInterface::class); + $responseMock = Mockery::mock(ResponseInterface::class); + $responseBodyMock = Mockery::mock(StreamInterface::class); $responseBody = ['results' => 'yay']; $this->clientMock->shouldReceive('sendRequest')-> once()-> - with(Mockery::type(\GuzzleHttp\Psr7\Request::class))-> + with(Mockery::type(RequestInterface::class))-> andReturn($responseMock); $responseMock->shouldReceive('getStatusCode')->andReturn(200); From 5aeb6087e2cbe9383788f22f3d64a979fe6aab5c Mon Sep 17 00:00:00 2001 From: virtualLast Date: Thu, 12 Feb 2026 21:06:37 +0000 Subject: [PATCH 05/12] added documentation --- test/unit/SparkPostResponseTest.php | 84 ++++++++++++++++++ test/unit/SparkPostTest.php | 129 ++++++++++++++++++++++++++++ test/unit/TransmissionTest.php | 39 +++++++++ 3 files changed, 252 insertions(+) diff --git a/test/unit/SparkPostResponseTest.php b/test/unit/SparkPostResponseTest.php index 7fe60a0..03485d6 100644 --- a/test/unit/SparkPostResponseTest.php +++ b/test/unit/SparkPostResponseTest.php @@ -22,6 +22,12 @@ public function setUp(): void $this->responseMock = Mockery::mock(ResponseInterface::class); } + /** + * Test that getProtocolVersion returns the protocol version from the wrapped response. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for getProtocolVersion, and verifies the wrapper returns the same value. + */ public function testGetProtocolVersion(): void { $this->responseMock->shouldReceive('getProtocolVersion')->andReturn($this->returnValue); @@ -29,6 +35,12 @@ public function testGetProtocolVersion(): void $this->assertEquals($this->responseMock->getProtocolVersion(), $sparkpostResponse->getProtocolVersion()); } + /** + * Test that withProtocolVersion returns a new instance with the specified protocol version. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for withProtocolVersion, and verifies the returned object matches the mock result. + */ public function testWithProtocolVersion(): void { $param = 'protocol version'; @@ -42,6 +54,12 @@ public function testWithProtocolVersion(): void ); } + /** + * Test that getHeaders returns the headers from the wrapped response. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for getHeaders, and verifies the wrapper returns the same array. + */ public function testGetHeaders(): void { $this->responseMock->shouldReceive('getHeaders')->andReturn([]); @@ -49,6 +67,12 @@ public function testGetHeaders(): void $this->assertEquals($this->responseMock->getHeaders(), $sparkpostResponse->getHeaders()); } + /** + * Test that hasHeader returns whether a header exists in the wrapped response. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for hasHeader, and verifies the wrapper returns the expected boolean/value. + */ public function testHasHeader(): void { $param = 'header'; @@ -58,6 +82,12 @@ public function testHasHeader(): void $this->assertEquals($this->responseMock->hasHeader($param), $sparkpostResponse->hasHeader($param)); } + /** + * Test that getHeader returns the specified header from the wrapped response. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for getHeader, and verifies the wrapper returns the same array. + */ public function testGetHeader(): void { $param = 'header'; @@ -67,6 +97,12 @@ public function testGetHeader(): void $this->assertEquals($this->responseMock->getHeader($param), $sparkpostResponse->getHeader($param)); } + /** + * Test that getHeaderLine returns the specified header line from the wrapped response. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for getHeaderLine, and verifies the wrapper returns the same string. + */ public function testGetHeaderLine(): void { $param = 'header'; @@ -76,6 +112,12 @@ public function testGetHeaderLine(): void $this->assertEquals($this->responseMock->getHeaderLine($param), $sparkpostResponse->getHeaderLine($param)); } + /** + * Test that withHeader returns a new instance with the specified header. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for withHeader, and verifies the returned object matches the mock result. + */ public function testWithHeader(): void { $param = 'header'; @@ -90,6 +132,12 @@ public function testWithHeader(): void ); } + /** + * Test that withAddedHeader returns a new instance with the specified header added. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for withAddedHeader, and verifies the returned object matches the mock result. + */ public function testWithAddedHeader(): void { $param = 'header'; @@ -104,6 +152,12 @@ public function testWithAddedHeader(): void ); } + /** + * Test that withoutHeader returns a new instance without the specified header. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for withoutHeader, and verifies the returned object matches the mock result. + */ public function testWithoutHeader(): void { $param = 'header'; @@ -114,6 +168,12 @@ public function testWithoutHeader(): void $this->assertEquals($this->responseMock->withoutHeader($param), $sparkpostResponse->withoutHeader($param)); } + /** + * Test that getRequest returns the request data passed during construction. + * + * Why: Ensures that the SparkPostResponse correctly stores and retrieves the request metadata used for the API call. + * How: Initializes SparkPostResponse with a request array and verifies getRequest() returns that same array. + */ public function testGetRequest(): void { $request = ['some' => 'request']; @@ -122,6 +182,12 @@ public function testGetRequest(): void $this->assertEquals($sparkpostResponse->getRequest(), $request); } + /** + * Test that withBody returns a new instance with the specified body. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for withBody, and verifies the returned object matches the mock result. + */ public function testWithBody(): void { $param = Mockery::mock(StreamInterface::class); @@ -132,6 +198,12 @@ public function testWithBody(): void $this->assertEquals($this->responseMock->withBody($param), $sparkpostResponse->withBody($param)); } + /** + * Test that getStatusCode returns the status code from the wrapped response. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for getStatusCode, and verifies the wrapper returns the same integer. + */ public function testGetStatusCode(): void { $this->responseMock->shouldReceive('getStatusCode')->andReturn(200); @@ -139,6 +211,12 @@ public function testGetStatusCode(): void $this->assertEquals($this->responseMock->getStatusCode(), $sparkpostResponse->getStatusCode()); } + /** + * Test that withStatus returns a new instance with the specified status code. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for withStatus, and verifies the returned object matches the mock result. + */ public function testWithStatus(): void { $param = 200; @@ -148,6 +226,12 @@ public function testWithStatus(): void $this->assertEquals($this->responseMock->withStatus($param), $sparkpostResponse->withStatus($param)); } + /** + * Test that getReasonPhrase returns the reason phrase from the wrapped response. + * + * Why: Ensures that the SparkPostResponse correctly delegates the call to the underlying PSR-7 response object. + * How: Mocks ResponseInterface, sets expectation for getReasonPhrase, and verifies the wrapper returns the same string. + */ public function testGetReasonPhrase(): void { $this->responseMock->shouldReceive('getReasonPhrase')->andReturn($this->returnValue); diff --git a/test/unit/SparkPostTest.php b/test/unit/SparkPostTest.php index 952d911..a170e15 100644 --- a/test/unit/SparkPostTest.php +++ b/test/unit/SparkPostTest.php @@ -102,6 +102,11 @@ public function tearDown(): void } /** + * Test that request() returns a SparkPostResponse when async option is false. + * + * Why: Ensures the SparkPost client correctly handles synchronous request mode. + * How: Sets 'async' to false, mocks the client's sendRequest method, and asserts the returned object is a SparkPostResponse. + * * @throws SparkPostException * @throws \Exception */ @@ -117,6 +122,11 @@ public function testRequestSync(): void } /** + * Test that request() returns a SparkPostPromise when async option is true. + * + * Why: Ensures the SparkPost client correctly handles asynchronous request mode. + * How: Sets 'async' to true, mocks the client's sendAsyncRequest method, and asserts the returned object is a SparkPostPromise. + * * @throws SparkPostException * @throws \Exception */ @@ -133,6 +143,11 @@ public function testRequestAsync(): void } /** + * Test that the debug option, when false, does not include request data in the response. + * + * Why: Verifies that sensitive or large request data is not attached to the response object by default. + * How: Sets 'debug' to false, executes a request, and asserts that getRequest() on the resulting response returns null. + * * @throws SparkPostException * @throws \Exception */ @@ -147,6 +162,11 @@ public function testDebugOptionWhenFalse(): void } /** + * Test that the debug option, when true, includes request data in successful and failed responses. + * + * Why: Ensures developers can access original request parameters from response/exception objects for debugging. + * How: Sets 'debug' to true, executes requests (one success, one failure), and verifies that request data is correctly attached. + * * @throws SparkPostException * @throws \Exception */ @@ -171,6 +191,11 @@ public function testDebugOptionWhenTrue(): void } /** + * Test a successful synchronous request. + * + * Why: Verifies that syncRequest() correctly processes a successful API call. + * How: Mocks a successful sendRequest call and asserts the SparkPostResponse contains the expected body and status code. + * * @throws Exception * @throws SparkPostException */ @@ -187,6 +212,12 @@ public function testSuccessfulSyncRequest(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * Test an unsuccessful synchronous request. + * + * Why: Ensures that HTTP client exceptions are properly wrapped and re-thrown as SparkPostExceptions. + * How: Mocks a client exception during sendRequest and verifies that a SparkPostException is caught with correct error details. + */ public function testUnsuccessfulSyncRequest(): void { $this->clientMock->shouldReceive('sendRequest')-> @@ -203,6 +234,11 @@ public function testUnsuccessfulSyncRequest(): void } /** + * Test synchronous request with automatic retries on 5xx errors. + * + * Why: Verifies that the client correctly retries transient server errors. + * How: Mocks the client to return multiple 503 responses followed by a 200, and verifies the final success. + * * @throws Exception * @throws SparkPostException * @throws \Exception @@ -221,6 +257,11 @@ public function testSuccessfulSyncRequestWithRetries(): void } /** + * Test that synchronous retries are exhausted after the specified number of attempts. + * + * Why: Ensures the client doesn't retry indefinitely and eventually reports the failure. + * How: Mocks a persistent client exception and verifies that it is thrown after the configured retry limit is reached. + * * @throws Exception * @throws \Exception */ @@ -241,6 +282,11 @@ public function testUnsuccessfulSyncRequestWithRetries(): void } /** + * Test a successful asynchronous request followed by wait(). + * + * Why: Verifies the basic async request-response flow using promise wait(). + * How: Mocks an async request that returns a promise, and asserts that wait() on the returned SparkPostPromise yield correct data. + * * @throws SparkPostException * @throws \Exception */ @@ -256,6 +302,11 @@ public function testSuccessfulAsyncRequestWithWait(): void } /** + * Test an unsuccessful asynchronous request followed by wait(). + * + * Why: Ensures that async failures are correctly reported when wait() is called. + * How: Mocks an async request that returns a rejected promise and verifies wait() throws a SparkPostException. + * * @throws \Exception */ public function testUnsuccessfulAsyncRequestWithWait(): void @@ -273,6 +324,11 @@ public function testUnsuccessfulAsyncRequestWithWait(): void } /** + * Test a successful asynchronous request using then() callbacks. + * + * Why: Verifies that the onFulfilled callback is correctly triggered with a SparkPostResponse. + * How: Uses a Guzzle fulfilled promise and verifies the then() callback receives the expected status and body. + * * @throws \Throwable */ public function testSuccessfulAsyncRequestWithThen(): void @@ -290,6 +346,11 @@ public function testSuccessfulAsyncRequestWithThen(): void } /** + * Test an unsuccessful asynchronous request using then() callbacks. + * + * Why: Verifies that the onRejected callback is correctly triggered with a SparkPostException. + * How: Uses a Guzzle rejected promise and verifies the then() callback receives the expected error code and body. + * * @throws \Throwable */ public function testUnsuccessfulAsyncRequestWithThen(): void @@ -307,6 +368,11 @@ public function testUnsuccessfulAsyncRequestWithThen(): void } /** + * Test asynchronous request with automatic retries on 5xx errors. + * + * Why: Verifies that the async promise chain correctly handles transient errors via retries. + * How: Mocks multiple 503 responses in the async client and verifies the final successful response is delivered to the then() callback. + * * @throws \Throwable */ public function testSuccessfulAsyncRequestWithRetries(): void @@ -331,6 +397,11 @@ public function testSuccessfulAsyncRequestWithRetries(): void } /** + * Test that asynchronous retries are exhausted after the specified number of attempts. + * + * Why: Ensures the async chain eventually fails if the server is persistently broken. + * How: Mocks a rejected promise and verifies the final error is propagated to the then() rejection callback. + * * @throws \Throwable */ public function testUnsuccessfulAsyncRequestWithRetries(): void @@ -353,6 +424,11 @@ public function testUnsuccessfulAsyncRequestWithRetries(): void } /** + * Test that the SparkPostPromise correctly reports its state. + * + * Why: Verifies that state checks are correctly delegated to the underlying promise. + * How: Mocks the underlying promise's getState method and asserts the SparkPostPromise wrapper returns the same states. + * * @throws \Exception */ public function testPromise(): void @@ -366,6 +442,12 @@ public function testPromise(): void $this->assertEquals($this->promiseMock->getState(), $promise->getState()); } + /** + * Test that an exception is thrown when attempting an async request with a non-async client. + * + * Why: Prevents runtime errors by validating client capabilities early. + * How: Sets a synchronous-only mock client and verifies that calling asyncRequest throws an exception. + */ public function testUnsupportedAsyncRequest(): void { $this->expectException(\Exception::class); @@ -375,6 +457,12 @@ public function testUnsupportedAsyncRequest(): void $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); } + /** + * Test that HTTP headers are correctly constructed, including defaults and custom headers. + * + * Why: Ensures the API receives mandatory headers (Auth, Content-Type, User-Agent) correctly. + * How: Calls getHttpHeaders with a custom header and asserts all expected headers (default and custom) are present and correct. + */ public function testGetHttpHeaders(): void { $headers = $this->resource->getHttpHeaders([ @@ -389,6 +477,12 @@ public function testGetHttpHeaders(): void $this->assertEquals('php-sparkpost/' . $version, $headers['User-Agent']); } + /** + * Test that the API URL is correctly constructed with path and query parameters. + * + * Why: Ensures requests are sent to the correct API endpoint with properly formatted query strings. + * How: Calls getUrl with a path and array of parameters, and asserts the generated URL matches the expected format. + */ public function testGetUrl(): void { $url = 'https://api.sparkpost.com:443/api/v1/transmissions?key=value 1,value 2,value 3'; @@ -396,6 +490,12 @@ public function testGetUrl(): void $this->assertEquals($url, $testUrl); } + /** + * Test that a synchronous HTTP client can be set. + * + * Why: Ensures flexibility in choosing HTTP client implementations. + * How: Sets a mock HttpClient and uses reflection to verify the private property was updated. + */ public function testSetHttpClient(): void { $mock = Mockery::mock(HttpClient::class); @@ -403,6 +503,12 @@ public function testSetHttpClient(): void $this->assertEquals($mock, NSA::getProperty($this->resource, 'httpClient')); } + /** + * Test that an asynchronous HTTP client can be set. + * + * Why: Ensures flexibility in choosing async-capable HTTP client implementations. + * How: Sets a mock HttpAsyncClient and uses reflection to verify the private property was updated. + */ public function testSetHttpAsyncClient(): void { $mock = Mockery::mock(HttpAsyncClient::class); @@ -410,6 +516,12 @@ public function testSetHttpAsyncClient(): void $this->assertEquals($mock, NSA::getProperty($this->resource, 'httpClient')); } + /** + * Test that an exception is thrown when setting an invalid HTTP client. + * + * Why: Ensures type safety and prevents runtime errors from incompatible client objects. + * How: Attempts to set an invalid object (stdClass) as the HTTP client and asserts an exception is thrown. + */ public function testSetHttpClientException(): void { $this->expectException(\Exception::class); @@ -418,6 +530,11 @@ public function testSetHttpClientException(): void } /** + * Test that options can be initialized using just a string as the API key. + * + * Why: Provides a convenient shorthand for common client initialization. + * How: Passes a string to setOptions and verifies the 'key' option is correctly set while others remain at defaults. + * * @throws \Exception */ public function testSetOptionsStringKey(): void @@ -427,6 +544,12 @@ public function testSetOptionsStringKey(): void $this->assertEquals('SPARKPOST_API_KEY', $options['key']); } + /** + * Test that an exception is thrown if no API key is provided in the options. + * + * Why: Ensures the client cannot be misconfigured without a mandatory API key. + * How: Attempts to set options without a 'key' and asserts that an exception is thrown. + */ public function testSetBadOptions(): void { $this->expectException(\Exception::class); @@ -435,6 +558,12 @@ public function testSetBadOptions(): void $this->resource->setOptions(['not' => 'SPARKPOST_API_KEY']); } + /** + * Test that a custom PSR-17 Request Factory can be set. + * + * Why: Allows developers to use their preferred PSR-17 implementation. + * How: Sets a mock RequestFactoryInterface and verifies it is retrieved by the internal factory getter. + */ public function testSetRequestFactory(): void { $messageFactory = Mockery::mock(RequestFactoryInterface::class); diff --git a/test/unit/TransmissionTest.php b/test/unit/TransmissionTest.php index 4424d6f..10465c9 100644 --- a/test/unit/TransmissionTest.php +++ b/test/unit/TransmissionTest.php @@ -71,6 +71,12 @@ public function tearDown(): void Mockery::close(); } + /** + * Test that an invalid email format in recipients throws an exception. + * + * Why: Verifies that the Transmission resource validates email addresses before sending the request to the API. + * How: Adds an invalid email to the payload and asserts that calling post() throws an Exception. + */ public function testInvalidEmailFormat(): void { $this->expectException(\Exception::class); @@ -83,6 +89,11 @@ public function testInvalidEmailFormat(): void } /** + * Test a GET request to the transmissions endpoint. + * + * Why: Verifies that the Transmission resource correctly delegates GET requests to the SparkPost client. + * How: Mocks a successful GET request and verifies the response matches the expected mock data. + * * @throws SparkPostException */ public function testGet(): void @@ -108,6 +119,11 @@ public function testGet(): void } /** + * Test a PUT request to the transmissions endpoint. + * + * Why: Verifies that the Transmission resource correctly delegates PUT requests to the SparkPost client. + * How: Mocks a successful PUT request and verifies the response matches the expected mock data. + * * @throws SparkPostException */ public function testPut(): void @@ -132,6 +148,12 @@ public function testPut(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * Test a POST request to the transmissions endpoint. + * + * Why: Verifies that the Transmission resource correctly formats payloads and sends POST requests. + * How: Mocks a successful POST request and verifies the response matches the expected mock data. + */ public function testPost(): void { $responseMock = Mockery::mock(ResponseInterface::class); @@ -154,6 +176,12 @@ public function testPost(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * Test a POST request using a stored recipient list ID. + * + * Why: Ensures that when a list_id is used, individual recipient formatting is bypassed. + * How: Provides a list_id in the payload and verifies the POST request is successful without recipient validation errors. + */ public function testPostWithRecipientList(): void { $postTransmissionPayload = $this->postTransmissionPayload; @@ -180,6 +208,11 @@ public function testPostWithRecipientList(): void } /** + * Test a DELETE request to the transmissions endpoint. + * + * Why: Verifies that the Transmission resource correctly delegates DELETE requests to the SparkPost client. + * How: Mocks a successful DELETE request and verifies the response matches the expected mock data. + * * @throws SparkPostException */ public function testDelete(): void @@ -204,6 +237,12 @@ public function testDelete(): void $this->assertEquals(200, $response->getStatusCode()); } + /** + * Test the payload formatting logic. + * + * Why: Verifies the complex transformation of transmission payloads into the format required by the API. + * How: Passes a raw payload to formatPayload() and asserts the output matches a predefined correctly formatted JSON structure. + */ public function testFormatPayload(): void { $correctFormattedPayload = json_decode( From a0747372eb3d233ae6274d248db9ce20a1509ad5 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Mon, 16 Feb 2026 10:50:09 +0000 Subject: [PATCH 06/12] method argument casting --- lib/SparkPost/ResourceBase.php | 10 ++--- lib/SparkPost/SparkPost.php | 56 ++++++++++++---------------- lib/SparkPost/SparkPostException.php | 2 +- lib/SparkPost/Transmission.php | 2 +- 4 files changed, 31 insertions(+), 39 deletions(-) diff --git a/lib/SparkPost/ResourceBase.php b/lib/SparkPost/ResourceBase.php index 9bd5517..d4f2352 100644 --- a/lib/SparkPost/ResourceBase.php +++ b/lib/SparkPost/ResourceBase.php @@ -36,7 +36,7 @@ public function __construct(SparkPost $sparkpost, string $endpoint) * @throws SparkPostException * @see SparkPost->request() */ - public function get($uri = '', $payload = [], $headers = []) + public function get(string $uri = '', array $payload = [], array $headers = []) { return $this->request('GET', $uri, $payload, $headers); } @@ -48,7 +48,7 @@ public function get($uri = '', $payload = [], $headers = []) * @throws SparkPostException * @see SparkPost->request() */ - public function put($uri = '', $payload = [], $headers = []) + public function put(string $uri = '', array $payload = [], array $headers = []) { return $this->request('PUT', $uri, $payload, $headers); } @@ -60,7 +60,7 @@ public function put($uri = '', $payload = [], $headers = []) * @throws SparkPostException * @see SparkPost->request() */ - public function post($payload = [], $headers = []) + public function post(array $payload = [], array $headers = []) { return $this->request('POST', '', $payload, $headers); } @@ -72,7 +72,7 @@ public function post($payload = [], $headers = []) * @throws SparkPostException * @see SparkPost->request() */ - public function delete($uri = '', $payload = [], $headers = []) + public function delete(string $uri = '', array $payload = [], array $headers = []) { return $this->request('DELETE', $uri, $payload, $headers); } @@ -85,7 +85,7 @@ public function delete($uri = '', $payload = [], $headers = []) * @see SparkPost->request() * */ - public function request($method = 'GET', $uri = '', $payload = [], $headers = []) + public function request(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []) { if (is_array($uri)) { $headers = $payload; diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 5dd429a..1b211bf 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -6,15 +6,15 @@ use Http\Client\HttpClient; use Http\Client\HttpAsyncClient; use Http\Discovery\Psr17FactoryDiscovery; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamFactoryInterface; class SparkPost { - /** - * @var string Library version, used for setting User-Agent - */ + private string $version = '2.3.0'; /** @@ -22,19 +22,10 @@ class SparkPost */ private $httpClient; - /** - * @var RequestFactoryInterface - */ private RequestFactoryInterface $requestFactory; - /** - * @var StreamFactoryInterface - */ private StreamFactoryInterface $streamFactory; - /** - * @var array Options for requests - */ private array $options; /** @@ -51,9 +42,6 @@ class SparkPost 'retries' => 0 ]; - /** - * @var Transmission Instance of Transmission class - */ public Transmission $transmissions; /** @@ -80,7 +68,7 @@ public function __construct(HttpClient $httpClient, array $options) * * @return SparkPostPromise|SparkPostResponse Promise or Response depending on sync or async request * @throws SparkPostException - * @throws \Exception + * @throws \Exception|Exception */ public function request(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []) { @@ -101,7 +89,7 @@ public function request(string $method = 'GET', string $uri = '', array $payload * * @return SparkPostResponse * - * @throws SparkPostException|Exception + * @throws SparkPostException|Exception|ClientExceptionInterface */ public function syncRequest(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []): SparkPostResponse { @@ -122,9 +110,9 @@ public function syncRequest(string $method = 'GET', string $uri = '', array $pay } /** - * @throws Exception + * @throws Exception|ClientExceptionInterface */ - private function syncReqWithRetry($request, $retries) + private function syncReqWithRetry( RequestInterface $request, int $retries): ResponseInterface { $resp = $this->httpClient->sendRequest($request); $status = $resp->getStatusCode(); @@ -145,8 +133,12 @@ private function syncReqWithRetry($request, $retries) * @return SparkPostPromise * @throws \Exception */ - public function asyncRequest(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []): SparkPostPromise - { + public function asyncRequest( + string $method = 'GET', + string $uri = '', + array $payload = [], + array $headers = [] + ): SparkPostPromise { if ($this->httpClient instanceof HttpAsyncClient) { $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); $request = call_user_func_array([$this, 'buildRequestInstance'], $requestValues); @@ -172,7 +164,7 @@ public function asyncRequest(string $method = 'GET', string $uri = '', array $pa /** * @throws \Exception */ - private function asyncReqWithRetry($request, $retries) + private function asyncReqWithRetry(RequestInterface $request, int $retries) { return $this->httpClient->sendAsyncRequest($request)->then(function ($response) use ($request, $retries) { $status = $response->getStatusCode(); @@ -221,13 +213,13 @@ public function buildRequestValues(string $method, string $uri, array $payload, /** * Build RequestInterface from given params. * - * @param $method - * @param $url - * @param $headers - * @param $body + * @param string $method + * @param string $url + * @param array $headers + * @param array|string $body * @return RequestInterface */ - public function buildRequestInstance($method, $url, $headers, $body): RequestInterface + public function buildRequestInstance(string $method, string $url, array $headers, $body): RequestInterface { $request = $this->getRequestFactory()->createRequest($method, $url); @@ -245,13 +237,13 @@ public function buildRequestInstance($method, $url, $headers, $body): RequestInt /** * Build RequestInterface from given params. * - * @param $method - * @param $uri - * @param $payload - * @param $headers + * @param string $method + * @param string $uri + * @param array $payload + * @param array $headers * @return RequestInterface */ - public function buildRequest($method, $uri, $payload, $headers): RequestInterface + public function buildRequest(string $method, string $uri, array $payload, array $headers): RequestInterface { $requestValues = $this->buildRequestValues($method, $uri, $payload, $headers); return call_user_func_array([$this, 'buildRequestInstance'], $requestValues); diff --git a/lib/SparkPost/SparkPostException.php b/lib/SparkPost/SparkPostException.php index 4bf614b..24ed467 100644 --- a/lib/SparkPost/SparkPostException.php +++ b/lib/SparkPost/SparkPostException.php @@ -9,7 +9,7 @@ class SparkPostException extends \Exception /** * Variable to hold json decoded body from http response. */ - private $body = null; + private ?array $body = null; /** * Array with the request values sent. diff --git a/lib/SparkPost/Transmission.php b/lib/SparkPost/Transmission.php index c77e1eb..8b1205d 100644 --- a/lib/SparkPost/Transmission.php +++ b/lib/SparkPost/Transmission.php @@ -166,7 +166,7 @@ private function toAddressObject($address): array /** * Takes the longhand form of an email address and converts it to the shorthand form. * - * @param $address - the longhand form of an email address [ "name" => "John", "email" => "john@exmmple.com" ] + * @param string|array $address - the longhand form of an email address [ "name" => "John", "email" => "john@exmmple.com" ] */ private function toAddressString($address): string { From 3e9c52322f9d8fc07eca2c44b296b6e2febff82a Mon Sep 17 00:00:00 2001 From: virtualLast Date: Mon, 16 Feb 2026 13:38:32 +0000 Subject: [PATCH 07/12] typing --- lib/SparkPost/ResourceBase.php | 10 +++++++--- lib/SparkPost/SparkPost.php | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/SparkPost/ResourceBase.php b/lib/SparkPost/ResourceBase.php index d4f2352..d584642 100644 --- a/lib/SparkPost/ResourceBase.php +++ b/lib/SparkPost/ResourceBase.php @@ -2,6 +2,8 @@ namespace SparkPost; +use Http\Client\Exception; + /** * Class ResourceBase. */ @@ -33,7 +35,7 @@ public function __construct(SparkPost $sparkpost, string $endpoint) * Sends get request to API at the set endpoint. * * @return SparkPostPromise|SparkPostResponse - * @throws SparkPostException + * @throws SparkPostException|Exception * @see SparkPost->request() */ public function get(string $uri = '', array $payload = [], array $headers = []) @@ -45,7 +47,7 @@ public function get(string $uri = '', array $payload = [], array $headers = []) * Sends put request to API at the set endpoint. * * @return SparkPostPromise|SparkPostResponse - * @throws SparkPostException + * @throws SparkPostException|Exception * @see SparkPost->request() */ public function put(string $uri = '', array $payload = [], array $headers = []) @@ -58,6 +60,7 @@ public function put(string $uri = '', array $payload = [], array $headers = []) * * @return SparkPostPromise|SparkPostResponse * @throws SparkPostException + * @throws Exception * @see SparkPost->request() */ public function post(array $payload = [], array $headers = []) @@ -69,7 +72,7 @@ public function post(array $payload = [], array $headers = []) * Sends delete request to API at the set endpoint. * * @return SparkPostPromise|SparkPostResponse - * @throws SparkPostException + * @throws SparkPostException|Exception * @see SparkPost->request() */ public function delete(string $uri = '', array $payload = [], array $headers = []) @@ -82,6 +85,7 @@ public function delete(string $uri = '', array $payload = [], array $headers = [ * * @return SparkPostPromise|SparkPostResponse depending on sync or async request * @throws SparkPostException + * @throws Exception * @see SparkPost->request() * */ diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 1b211bf..4098290 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -341,7 +341,11 @@ public function setOptions($options): self } // Validate API key because its required - if (!isset($this->options['key']) && (!isset($options['key']) || !preg_match('/\S/', $options['key']))) { + if ( + !isset($this->options['key']) + && (!isset($options['key']) + || !preg_match('/\S/', $options['key']) + )) { throw new \Exception('You must provide an API key'); } From 65562c7093ca5b2ee98e4b7fcc20aff1c29f8642 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Mon, 16 Feb 2026 13:49:31 +0000 Subject: [PATCH 08/12] removed httpClient deprecation --- lib/SparkPost/ResourceBase.php | 19 ++++++++++--------- lib/SparkPost/SparkPost.php | 16 ++++++++-------- lib/SparkPost/SparkPostPromise.php | 6 +++--- test/unit/SparkPostTest.php | 14 +++++++------- test/unit/TransmissionTest.php | 5 +++-- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/SparkPost/ResourceBase.php b/lib/SparkPost/ResourceBase.php index d584642..9197935 100644 --- a/lib/SparkPost/ResourceBase.php +++ b/lib/SparkPost/ResourceBase.php @@ -3,6 +3,7 @@ namespace SparkPost; use Http\Client\Exception; +use Psr\Http\Client\ClientExceptionInterface; /** * Class ResourceBase. @@ -35,10 +36,10 @@ public function __construct(SparkPost $sparkpost, string $endpoint) * Sends get request to API at the set endpoint. * * @return SparkPostPromise|SparkPostResponse - * @throws SparkPostException|Exception + * @throws SparkPostException|Exception|ClientExceptionInterface * @see SparkPost->request() */ - public function get(string $uri = '', array $payload = [], array $headers = []) + public function get($uri = '', array $payload = [], array $headers = []) { return $this->request('GET', $uri, $payload, $headers); } @@ -47,10 +48,10 @@ public function get(string $uri = '', array $payload = [], array $headers = []) * Sends put request to API at the set endpoint. * * @return SparkPostPromise|SparkPostResponse - * @throws SparkPostException|Exception + * @throws SparkPostException|Exception|ClientExceptionInterface * @see SparkPost->request() */ - public function put(string $uri = '', array $payload = [], array $headers = []) + public function put($uri = '', array $payload = [], array $headers = []) { return $this->request('PUT', $uri, $payload, $headers); } @@ -60,7 +61,7 @@ public function put(string $uri = '', array $payload = [], array $headers = []) * * @return SparkPostPromise|SparkPostResponse * @throws SparkPostException - * @throws Exception + * @throws Exception|ClientExceptionInterface * @see SparkPost->request() */ public function post(array $payload = [], array $headers = []) @@ -72,10 +73,10 @@ public function post(array $payload = [], array $headers = []) * Sends delete request to API at the set endpoint. * * @return SparkPostPromise|SparkPostResponse - * @throws SparkPostException|Exception + * @throws SparkPostException|Exception|ClientExceptionInterface * @see SparkPost->request() */ - public function delete(string $uri = '', array $payload = [], array $headers = []) + public function delete($uri = '', array $payload = [], array $headers = []) { return $this->request('DELETE', $uri, $payload, $headers); } @@ -85,11 +86,11 @@ public function delete(string $uri = '', array $payload = [], array $headers = [ * * @return SparkPostPromise|SparkPostResponse depending on sync or async request * @throws SparkPostException - * @throws Exception + * @throws Exception|ClientExceptionInterface * @see SparkPost->request() * */ - public function request(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []) + public function request(string $method = 'GET', $uri = '', array $payload = [], array $headers = []) { if (is_array($uri)) { $headers = $payload; diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 4098290..4e49c10 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -3,10 +3,10 @@ namespace SparkPost; use Http\Client\Exception; -use Http\Client\HttpClient; use Http\Client\HttpAsyncClient; use Http\Discovery\Psr17FactoryDiscovery; use Psr\Http\Client\ClientExceptionInterface; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -18,7 +18,7 @@ class SparkPost private string $version = '2.3.0'; /** - * @var HttpClient|HttpAsyncClient used to make requests + * @var ClientInterface|HttpAsyncClient used to make requests */ private $httpClient; @@ -47,11 +47,11 @@ class SparkPost /** * Sets up the SparkPost instance. * - * @param HttpClient $httpClient - An httplug client or adapter + * @param ClientInterface $httpClient - An httplug client or adapter * @param array $options - An array to overide default options or a string to be used as an API key * @throws \Exception */ - public function __construct(HttpClient $httpClient, array $options) + public function __construct(ClientInterface $httpClient, array $options) { $this->setOptions($options); $this->setHttpClient($httpClient); @@ -68,7 +68,7 @@ public function __construct(HttpClient $httpClient, array $options) * * @return SparkPostPromise|SparkPostResponse Promise or Response depending on sync or async request * @throws SparkPostException - * @throws \Exception|Exception + * @throws \Exception|Exception|ClientExceptionInterface */ public function request(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []) { @@ -300,17 +300,17 @@ public function getUrl(string $path, array $params = []): string /** * Sets $httpClient to be used for request. * - * @param HttpClient|HttpAsyncClient $httpClient - the client to be used for request + * @param ClientInterface|HttpAsyncClient $httpClient - the client to be used for request * * @return SparkPost */ public function setHttpClient($httpClient): self { - if (!$httpClient instanceof HttpClient && !$httpClient instanceof HttpAsyncClient) { + if (!$httpClient instanceof ClientInterface && !$httpClient instanceof HttpAsyncClient) { throw new \LogicException( sprintf( 'Parameter to SparkPost::setHttpClient must be instance of "%s" or "%s"', - HttpClient::class, + ClientInterface::class, HttpAsyncClient::class ) ); diff --git a/lib/SparkPost/SparkPostPromise.php b/lib/SparkPost/SparkPostPromise.php index 6c2d66b..2105761 100644 --- a/lib/SparkPost/SparkPostPromise.php +++ b/lib/SparkPost/SparkPostPromise.php @@ -20,9 +20,9 @@ class SparkPostPromise implements HttpPromise * set the promise to be wrapped. * * @param HttpPromise $promise - * @param null $request + * @param array|null $request */ - public function __construct(HttpPromise $promise, $request = null) + public function __construct(HttpPromise $promise, ?array $request = null) { $this->promise = $promise; $this->request = $request; @@ -35,7 +35,7 @@ public function __construct(HttpPromise $promise, $request = null) * @param callable|null $onRejected - function to be called if the promise is rejected * @return HttpPromise */ - public function then(callable $onFulfilled = null, callable $onRejected = null): HttpPromise + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): HttpPromise { $request = $this->request; diff --git a/test/unit/SparkPostTest.php b/test/unit/SparkPostTest.php index a170e15..a07bc1a 100644 --- a/test/unit/SparkPostTest.php +++ b/test/unit/SparkPostTest.php @@ -5,8 +5,8 @@ use Http\Client\Exception; use Http\Client\Exception\HttpException; use Http\Client\HttpAsyncClient; -use Http\Client\HttpClient; use Http\Promise\Promise; +use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\RequestInterface; use Nyholm\NSA; @@ -88,7 +88,7 @@ public function setUp(): void $this->promiseMock = Mockery::mock(Promise::class); //setup mock for the adapter - $this->clientMock = Mockery::mock(HttpClient::class, HttpAsyncClient::class); + $this->clientMock = Mockery::mock(ClientInterface::class, HttpAsyncClient::class); $this->clientMock->shouldReceive('sendAsyncRequest') ->with(Mockery::type(RequestInterface::class)) ->andReturn($this->promiseMock); @@ -378,7 +378,7 @@ public function testUnsuccessfulAsyncRequestWithThen(): void public function testSuccessfulAsyncRequestWithRetries(): void { $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); - $clientMock = Mockery::mock(HttpClient::class, HttpAsyncClient::class); + $clientMock = Mockery::mock(ClientInterface::class, HttpAsyncClient::class); $clientMock->shouldReceive('sendAsyncRequest') ->with(Mockery::type(RequestInterface::class)) ->andReturn( @@ -408,7 +408,7 @@ public function testUnsuccessfulAsyncRequestWithRetries(): void { $testReq = $this->resource->buildRequest('POST', 'transmissions', $this->postTransmissionPayload, []); $rejectedPromise = new GuzzleRejectedPromise($this->exceptionMock); - $clientMock = Mockery::mock(HttpClient::class, HttpAsyncClient::class); + $clientMock = Mockery::mock(ClientInterface::class, HttpAsyncClient::class); $clientMock->shouldReceive('sendAsyncRequest') ->with(Mockery::type(RequestInterface::class)) ->andReturn(new GuzzleAdapterPromise($rejectedPromise, $testReq)); @@ -452,7 +452,7 @@ public function testUnsupportedAsyncRequest(): void { $this->expectException(\Exception::class); - $this->resource->setHttpClient(Mockery::mock(HttpClient::class)); + $this->resource->setHttpClient(Mockery::mock(ClientInterface::class)); $this->resource->asyncRequest('POST', 'transmissions', $this->postTransmissionPayload); } @@ -494,11 +494,11 @@ public function testGetUrl(): void * Test that a synchronous HTTP client can be set. * * Why: Ensures flexibility in choosing HTTP client implementations. - * How: Sets a mock HttpClient and uses reflection to verify the private property was updated. + * How: Sets a mock ClientInterface and uses reflection to verify the private property was updated. */ public function testSetHttpClient(): void { - $mock = Mockery::mock(HttpClient::class); + $mock = Mockery::mock(ClientInterface::class); $this->resource->setHttpClient($mock); $this->assertEquals($mock, NSA::getProperty($this->resource, 'httpClient')); } diff --git a/test/unit/TransmissionTest.php b/test/unit/TransmissionTest.php index 10465c9..4a77f51 100644 --- a/test/unit/TransmissionTest.php +++ b/test/unit/TransmissionTest.php @@ -8,7 +8,8 @@ use SparkPost\SparkPost; use Mockery; use Psr\Http\Message\RequestInterface; -use Http\Client\HttpClient; +use Http\Client\HttpAsyncClient; +use Psr\Http\Client\ClientInterface; use SparkPost\SparkPostException; class TransmissionTest extends TestCase @@ -61,7 +62,7 @@ class TransmissionTest extends TestCase public function setUp(): void { //setup mock for the adapter - $this->clientMock = Mockery::mock(HttpClient::class); + $this->clientMock = Mockery::mock(ClientInterface::class); $this->resource = new SparkPost($this->clientMock, ['key' => 'SPARKPOST_API_KEY', 'async' => false]); } From cd0dfad0c5721aae9dce93b56c6c0929a26020e9 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Mon, 16 Feb 2026 14:06:30 +0000 Subject: [PATCH 09/12] updated fixer package which resolved final composer audit warnings --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 65a7525..61ede6d 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "mockery/mockery": "^1.3", "nyholm/nsa": "^1.0", "php-coveralls/php-coveralls": "^2.4", - "friendsofphp/php-cs-fixer": "^2.18" + "friendsofphp/php-cs-fixer": "^3.9" }, "autoload": { "psr-4": { From 265d753816187a98ee5b19b9ee528465a14b1e4e Mon Sep 17 00:00:00 2001 From: virtualLast Date: Mon, 16 Feb 2026 15:00:44 +0000 Subject: [PATCH 10/12] fix for json decode issue --- lib/SparkPost/SparkPost.php | 5 +++++ test/unit/IssueReproductionTest.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 test/unit/IssueReproductionTest.php diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 4e49c10..a43ce04 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -202,6 +202,11 @@ public function buildRequestValues(string $method, string $uri, array $payload, // old form-feed workaround now removed $body = json_encode($body); + + if ($body === false) { + throw new \Exception('JSON encoding error: ' . json_last_error_msg()); + } + return [ 'method' => $method, 'url' => $url, diff --git a/test/unit/IssueReproductionTest.php b/test/unit/IssueReproductionTest.php new file mode 100644 index 0000000..48533c9 --- /dev/null +++ b/test/unit/IssueReproductionTest.php @@ -0,0 +1,28 @@ + 'test-key']); + + // Invalid UTF-8 sequence to make json_encode return false + $payload = ['invalid' => "\xB1\x31"]; + + // We expect an Exception because json_encode fails + try { + $sparkpost->buildRequest('POST', 'test', $payload, []); + $this->fail('Expected \Exception was not thrown'); + } catch (\Exception $e) { + $this->assertStringContainsString('JSON encoding error', $e->getMessage()); + } + } +} From 57756cab2421690bb73f0400fc1a5b1d211472b2 Mon Sep 17 00:00:00 2001 From: virtualLast Date: Mon, 16 Feb 2026 15:04:32 +0000 Subject: [PATCH 11/12] fix for potentially makiung unauthenticated api requestes --- lib/SparkPost/SparkPost.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index a43ce04..95fe4e4 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -346,11 +346,8 @@ public function setOptions($options): self } // Validate API key because its required - if ( - !isset($this->options['key']) - && (!isset($options['key']) - || !preg_match('/\S/', $options['key']) - )) { + $keyToValidate = $options['key'] ?? $this->options['key'] ?? ''; + if (!preg_match('/\S/', $keyToValidate)) { throw new \Exception('You must provide an API key'); } From f037c4d035a3e6db445c4dac21204b5542c3b7de Mon Sep 17 00:00:00 2001 From: virtualLast Date: Mon, 16 Feb 2026 15:17:41 +0000 Subject: [PATCH 12/12] more fixes --- lib/SparkPost/SparkPost.php | 9 ++++++--- lib/SparkPost/SparkPostException.php | 6 +++--- lib/SparkPost/SparkPostPromise.php | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/SparkPost/SparkPost.php b/lib/SparkPost/SparkPost.php index 95fe4e4..aa04c4b 100644 --- a/lib/SparkPost/SparkPost.php +++ b/lib/SparkPost/SparkPost.php @@ -68,7 +68,7 @@ public function __construct(ClientInterface $httpClient, array $options) * * @return SparkPostPromise|SparkPostResponse Promise or Response depending on sync or async request * @throws SparkPostException - * @throws \Exception|Exception|ClientExceptionInterface + * @throws \Exception */ public function request(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []) { @@ -89,7 +89,8 @@ public function request(string $method = 'GET', string $uri = '', array $payload * * @return SparkPostResponse * - * @throws SparkPostException|Exception|ClientExceptionInterface + * @throws SparkPostException + * @throws \Exception */ public function syncRequest(string $method = 'GET', string $uri = '', array $payload = [], array $headers = []): SparkPostResponse { @@ -104,7 +105,7 @@ public function syncRequest(string $method = 'GET', string $uri = '', array $pay $request ); return new SparkPostResponse($resp, $this->ifDebug($requestValues)); - } catch (\Exception $exception) { + } catch (\Throwable $exception) { throw new SparkPostException($exception, $this->ifDebug($requestValues)); } } @@ -184,6 +185,7 @@ private function asyncReqWithRetry(RequestInterface $request, int $retries) * @param array $headers * * @return array $requestValues + * @throws \Exception */ public function buildRequestValues(string $method, string $uri, array $payload, array $headers): array { @@ -247,6 +249,7 @@ public function buildRequestInstance(string $method, string $url, array $headers * @param array $payload * @param array $headers * @return RequestInterface + * @throws \Exception */ public function buildRequest(string $method, string $uri, array $payload, array $headers): RequestInterface { diff --git a/lib/SparkPost/SparkPostException.php b/lib/SparkPost/SparkPostException.php index 24ed467..bbec4cb 100644 --- a/lib/SparkPost/SparkPostException.php +++ b/lib/SparkPost/SparkPostException.php @@ -19,10 +19,10 @@ class SparkPostException extends \Exception /** * Sets up the custom exception and copies over original exception values. * - * @param \Exception $exception - the exception to be wrapped + * @param \Throwable $exception - the exception to be wrapped * @param null $request */ - public function __construct(\Exception $exception, $request = null) + public function __construct(\Throwable $exception, $request = null) { $this->request = $request; @@ -34,7 +34,7 @@ public function __construct(\Exception $exception, $request = null) $code = $exception->getResponse()->getStatusCode(); } - parent::__construct($message, $code, $exception->getPrevious()); + parent::__construct($message, $code, $exception); } /** diff --git a/lib/SparkPost/SparkPostPromise.php b/lib/SparkPost/SparkPostPromise.php index 2105761..6d671bd 100644 --- a/lib/SparkPost/SparkPostPromise.php +++ b/lib/SparkPost/SparkPostPromise.php @@ -75,7 +75,7 @@ public function wait($unwrap = true): SparkPostResponse $response = $this->promise->wait($unwrap); return $response ? new SparkPostResponse($response, $this->request) : $response; - } catch (\Exception| \Throwable $exception) { + } catch (\Throwable $exception) { throw new SparkPostException($exception, $this->request); } }